android中 camera 拍照应用详解

来源:互联网 发布:java实验指导书答案 编辑:程序博客网 时间:2024/04/25 00:06


在安卓中使用拍照功能有两种方式,一是调用已有的拍照应用;二是使用androidCamera对象直接操作相机,自己写代码来实现拍照功能。

如果是采用Camera的方式,相当于自己写了个拍照程序。直接使用Camera的好处是拍照界面可以完全自定义,UI风格可以和自己应用保持一致,但也要麻烦一些。

下面来介绍开发一个拍照程序的步骤,实现方法参考了谷歌android开发的官方文档。

整个步骤大概分为三步:

1.启动相机,其实就是打开摄像头。

2.生成摄像的预览图像。

3.拍照

为了完成上述的三个步骤,我们至少需要建立以下三个类,并新建这三个类的对象,他们之间通过一定的关联,就能完成最基本的拍照功能。

Camera对象:管理硬件camera的打开和关闭,触发拍照命令。

一个继承自的SurfaceView相机图像预览类:因为官方文档把这个自定义的SurfaceView类命名为CameraPreview,在此我也这样命名。该类负责在打开相机的时候在activity中显示采集到的图像预览效果。该过程和UI主线程是异步处理的,因此使用SurfaceView

拍照数据捕获类:当用户点击某个按钮开始拍照的时候,需要有相应的方法能在拍照完成之后将图像数据捕获并存储,该类通过实现Camera.PictureCallback接口来完成捕获过程。


先展示一下拍照的界面:

界面很简单,只有两个部分,上面是图像预览区,下面是拍照按钮,xml布局代码如下:

<?xmlversion="1.0"encoding="utf-8"?>

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

<FrameLayout

android:id="@+id/camera_preview"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:layout_weight="1"

/>


<Button

android:id="@+id/button_capture"

android:text="Capture"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

/>

</LinearLayout>

预览区域我们采用的是FrameLayout,但是预览效果其实和FrameLayout没什么关系,能在FrameLayout中产生预览效果是因为我们往FrameLayout中添加了上面提到的一个继承自的SurfaceView相机图像预览类。添加该类到FrameLayout的代码在activityonCreate方法中,下面贴出整个activity的代码:

package com.example.cameratest;


import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.text.SimpleDateFormat;

import java.util.Date;


importandroid.net.Uri;

import android.os.Bundle;

import android.os.Environment;

importandroid.provider.MediaStore;

import android.app.Activity;

import android.content.Context;

importandroid.content.Intent;

import android.hardware.Camera;

import android.hardware.Camera.PictureCallback;

import android.util.Log;

importandroid.view.Menu;

import android.view.Surface;

import android.view.SurfaceHolder;

import android.view.SurfaceView;

import android.view.View;

import android.widget.Button;

import android.widget.FrameLayout;

importandroid.widget.Toast;


publicclass CameraActivity extends Activity {

publicstaticfinalintMEDIA_TYPE_IMAGE = 1;

publicstaticfinalintMEDIA_TYPE_VIDEO = 2;

protectedstaticfinal String TAG = null;

private Camera mCamera;

private CameraPreview mPreview;


@Override

publicvoid onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.camera_activity);


// Create an instance ofCamera

mCamera = getCameraInstance();


// Create our Preview viewand set it as the content of our activity.

mPreview = new CameraPreview(thismCamera);

FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);

preview.addView(mPreview);

Button captureButton = (Button) findViewById(R.id.button_capture);

captureButton.setOnClickListener(new View.OnClickListener() {

@Override

publicvoid onClick(View v) {

// get an imagefrom the camera

mCamera.takePicture(nullnullmPicture);

}

});


}


/** A safe way to get an instance of theCamera object. */

publicstatic Camera getCameraInstance() {

Camera c = null;

try {

c = Camera.open(); // attempt to get a Camera instance

catch (Exception e) {

// Camera is notavailable (in use or does not exist)

}

return c; // returns nullif camera is unavailable

}


/** A basic Camera preview class */

publicclass CameraPreview extends SurfaceView implements

SurfaceHolder.Callback {

private SurfaceHolder mHolder;

private Camera mCamera;


public CameraPreview(Context context, Camera camera) {

super(context);

mCamera = camera;


// Install aSurfaceHolder.Callback so we get notified when the

// underlyingsurface is created and destroyed.

mHolder = getHolder();

mHolder.addCallback(this);

// deprecatedsetting, but required on Android versions prior to 3.0

mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

}


publicvoid surfaceCreated(SurfaceHolder holder) {

// The Surfacehas been created, now tell the camera where to draw

// the preview.

try {

mCamera.setPreviewDisplay(holder);

mCamera.setDisplayOrientation(90);

mCamera.startPreview();

catch (IOException e) {

Log.d(TAG"Errorsetting camera preview: " + e.getMessage());

}

}


publicvoid surfaceDestroyed(SurfaceHolder holder) {

// empty. Takecare of releasing the Camera preview in your

// activity.

}


publicvoid surfaceChanged(SurfaceHolder holder, int format, int w,

int h) {

// If yourpreview can change or rotate, take care of those events

// here.

// Make sure tostop the preview before resizing or reformatting it.


if (mHolder.getSurface() == null) {

// previewsurface does not exist

return;

}


// stop previewbefore making changes

try {

mCamera.stopPreview();

catch (Exception e) {

// ignore: triedto stop a non-existent preview

}


// set previewsize and make any resize, rotate or

// reformattingchanges here


// start previewwith new settings

try {

mCamera.setPreviewDisplay(mHolder);

mCamera.setDisplayOrientation(90);

mCamera.startPreview();


catch (Exception e) {

Log.d(TAG"Errorstarting camera preview: " + e.getMessage());

}

}

}


private PictureCallback mPicture = new PictureCallback() {


@Override

publicvoid onPictureTaken(byte[] data, Camera camera) {


File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);

if (pictureFile == null) {


return;

}


try {

FileOutputStream fos = new FileOutputStream(pictureFile);

fos.write(data);

fos.close();

Log.i("cameratest""pictureFiledata=" + data.length);

catch (FileNotFoundException e) {

Log.i("cameratest""File notfound: " + e.getMessage());

catch (IOException e) {

Log.i("cameratest""Erroraccessing file: " + e.getMessage());

}

}

};


privatestatic File getOutputMediaFile(int type) {

// To be safe, you shouldcheck that the SDCard is mounted

// usingEnvironment.getExternalStorageState() before doing this.


File mediaStorageDir = new File(

Environment

.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),

"MyCameraApp");

// This location works bestif you want the created images to be shared

// between applications andpersist after your app has been uninstalled.


// Create the storagedirectory if it does not exist

if (!mediaStorageDir.exists()) {

if (!mediaStorageDir.mkdirs()) {

Log.d("MyCameraApp""failed tocreate directory");

returnnull;

}

}


// Create a media file name

String timeStamp = newSimpleDateFormat("yyyyMMdd_HHmmss")

.format(new Date());

File mediaFile;

if (type == MEDIA_TYPE_IMAGE) {

mediaFile = new File(mediaStorageDir.getPath() + File.separator

"IMG_" + timeStamp + ".jpg");

elseif (type == MEDIA_TYPE_VIDEO) {

mediaFile = new File(mediaStorageDir.getPath() + File.separator

"VID_" + timeStamp + ".mp4");

else {

returnnull;

}


return mediaFile;

}


@Override

protectedvoid onStop() {

Log.i("cameratest""onStop");

super.onStop(); // Always callthe superclass method first


}


@Override

publicvoid onPause() {

super.onPause(); // Alwayscall the superclass method first

Log.i("cameratest""onPause");

// Release the Camera becausewe don't need it when paused

// and other activities mightneed to use it.

if (mCamera != null) {

mCamera.release();

mCamera = null;

}

}

}

代码解读:

获得一个Camera对象不是通过new的方式,而是通过Camera.open()返回一个Camera对象,这一过程必须要有异常处理,为此我们将整个过程封装在getCameraInstance函数中,getCameraInstance函数的代码如下:

/** A safeway to get an instance of the Camera object. */

publicstatic Camera getCameraInstance(){

Camera c = null;

try {

c = Camera.open(); // attempt to get a Camera instance

}

catch (Exception e){

// Camera is not available (in use or does not exist)

}

return c; // returns null if camera is unavailable

}

mCamera = getCameraInstance();

然后在activityonCreate方法中我们mCamera作为参数传递给CameraPreview的构造函数,建立了相机图像预览类CameraPreview对象mPreview,将mPreview添加进FrameLayout容器中,如下:

mPreview = new CameraPreview(thismCamera);

FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);

preview.addView(mPreview);

CameraPreview的实现在上面给出得的activity代码中可以找到,其实他就是一个实现了SurfaceHolder.Callback接口的SurfaceView。关于如何使用SurfaceView这里不做讲解,这里只说明为什么我们仅仅是新建了个继承自SurfaceViewCameraPreview对象,然后将该对象放入布局文件中,就能实现预览效果。其中关键的代码是:

mHolder = getHolder();

mHolder.addCallback(this);

……

……

mCamera.setPreviewDisplay(mHolder);

将获得的mHoldermCamera关联起来。非常简单吧,其实android系统已经为我们做了绝大多数的工作。

通过这几步,我们已经能够得到一个相机拍照的界面,并且带有预览功能,但是好像还缺少点什么,对,还去少触发拍照的动作。

点击如上图所示的captrue按钮即开始触发拍照行为,在这里其实是调用mCamera takePicture方法:

ButtoncaptureButton = (Button) findViewById(R.id.button_capture);

captureButton.setOnClickListener(newView.OnClickListener() {

@Override

publicvoid onClick(View v) {

//get an image from the camera

mCamera.takePicture(null,null, mPicture);

}

});


我们注意到mCamera.takePicture(null,null, mPicture);mPicture这个变量还一直没有提到,他就是我们的拍照数据捕获类PictureCallback,它的任务很简单,就是在拍照完成后,处理图片,这里我们是将图片保存下来。onPictureTaken方法中可以通过data参数获得拍照的照片数据。我们将这些数据存储在公共目录的picture目录里面,就是存放相册图片的地方,获得这个存放地址的方法为getOutputMediaFile,具体的代码参见上面的activity的代码。


上面基本描述了拍照程序的实现流程,下面介绍一些细节。

1.默认情况下,拍照预览窗口的角度是横屏显示的。如果你的activity是竖屏状态,那么需要设置预览窗口的角度。本例子中我们将角度设置为90度,这样刚好能正确显示:

mCamera.setDisplayOrientation(90);

2.Camera不能同时被两个activity使用,否则会引起程序崩溃,所以需要在不需要camera的时候释放资源,一般我们是在onPause中释放。比如这个例子中:

@Override

publicvoid onPause() {

super.onPause(); // Alwayscall the superclass method first

// Release the Camera becausewe don't need it when paused

// and other activities mightneed to use it.

if (mCamera != null) {

mCamera.release();

mCamera = null;

}

}

0 0
原创粉丝点击