目录

效果展示
本次将要实现如下效果,点击屏幕下方的红圈实现拍照。

实现步骤
1.继承SurfaceView获取Camera图像预览
这里首先简单的说明一下为什么要用SurfaceView,抛去那些复杂的逻辑不讲,我们先看看Camera的预览显示的方法:
//下面为Camera中的源码
public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
if (holder != null) {
setPreviewSurface(holder.getSurface());
} else {
setPreviewSurface((Surface)null);
}
}
由上面的方法我们可以看到Camera的预览方法中需要传入一个SurfaceHolder的实现对象,而SurfaceView的getHolder()方法刚好返回一个SurfaceHolder的实现对象,因此我们可以通过SurfaceView来展示Camera的预览图像。
//一下为SurfaceView中的部分源码
public SurfaceHolder getHolder() {
return mSurfaceHolder;
}
接下来我们就先继承SurfaceView并实现SurfaceHolder.Callback这个接口(负责监听surface的创建、改变和销毁),在实现的方法中对Camera进行相应的处理。
public class TakePictureCameraView extends SurfaceView implements SurfaceHolder.Callback{
private Camera mCamera;//相机
private boolean isPreviewing;//是否在预览的标记
public TakePictureCameraView(Context context) {
super(context);
init();
}
public TakePictureCameraView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TakePictureCameraView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//添加回调函数,这是非常重要的,如果不加可能会产生黑屏的现象
getHolder().addCallback(this);
}
/**
* 返回预览状态
*/
public boolean isPreviewing() {
return isPreviewing;
}
/**
* 打开指定摄像头
*/
public void openCamera() {
//这里是获取到手机的后置摄像头并打开
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int cameraId = 0; cameraId < Camera.getNumberOfCameras(); cameraId++) {
Camera.getCameraInfo(cameraId, cameraInfo);
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
try {
mCamera = Camera.open(cameraId);
} catch (Exception e) {
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
}
break;
}
}
}
/**
* 加载相机配置
*/
private void initCamera() {
try {
mCamera.setPreviewDisplay(getHolder());//当前控件显示相机数据
startPreview();//打开相机(开始预览)
}catch (Exception e){
releaseCamera();
}
}
/**
* 释放相机
*/
private void releaseCamera() {
if(mCamera!=null){
stopPreview();
mCamera.release();//释放相机
mCamera=null;
}
}
/**
* 停止预览
*/
private void stopPreview() {
if (mCamera != null && isPreviewing) {
mCamera.stopPreview();//停止预览
isPreviewing = false;
}
}
/**
* 开始预览
*/
public void startPreview() {
if (mCamera != null) {
mCamera.startPreview();
isPreviewing = true;
}
}
/**
* surface创建时的回调
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
releaseCamera();
openCamera();
initCamera();
}catch (Exception e){
mCamera = null;
}
}
/**
* surface改变时的回调
*/
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
stopPreview();
initCamera();
}
/**
* surface销毁时的回调
*/
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
releaseCamera();
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.itfitness.idcardocr01.widget.TakePictureCameraView
android:id="@+id/camera"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
AndroidManifest文件加入如下权限:(如果系统为6.0以上的话别忘了动态申请权限)
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
此时如果不出意外的话会出现如下效果:(图像不清晰、角度不正确、图像拉伸严重)

2.纠正预览图像的角度
针对上面出现的问题我们首先纠正图像角度问题,这里只需要在initCamera方法中加入一行代码即可解决:
mCamera.setDisplayOrientation(90);//调整预览角度,旋转90度

3.匹配合适的分辨率
在上一步中虽然解决了图像角度问题但是仔细来看的话图像还是有点拉伸的,接下来我们就对分辨率进行适配,首先需要新增两个属性screenHeight和screenWidth然后在初始化init()方法中将屏幕的分辨率赋给这两个变量,然后与相机支持的分辨率对比得出最佳的分辨率。
private int screenHeight;//屏幕的高度分辨率
private int screenWidth;//屏幕的宽度分辨率
private void init() {
//获取屏幕分辨率
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
screenWidth = dm.heightPixels;
screenHeight = dm.widthPixels;
}
/**
* 配置相机参数
*/
private void setCameraParameters() {
Camera.Parameters parameters = mCamera.getParameters();
//获取所有支持的分辨率
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
//确定前面定义的预览宽高是camera支持的,不支持取就更大的
for (int i = 0; i < sizes.size(); i++) {
if ((sizes.get(i).width >= screenWidth && sizes.get(i).height >= screenHeight) || i == sizes.size() - 1) {
screenWidth = sizes.get(i).width;
screenHeight = sizes.get(i).height;
break;
}
}
//设置最终确定的预览大小
parameters.setPreviewSize(screenWidth, screenHeight);//设置预览分辨率
parameters.setPictureSize(screenWidth, screenHeight);//设置拍照图片的分辨率
mCamera.setParameters(parameters);//设置相机参数
}
我们将配置相机参数的方法放在initCamera()中执行:
/**
* 加载相机配置
*/
private void initCamera() {
try {
mCamera.setPreviewDisplay(getHolder());//当前控件显示相机数据
mCamera.setDisplayOrientation(90);//调整预览角度
setCameraParameters();//配置相机参数,匹配合适的分辨率
startPreview();//打开相机
}catch (Exception e){
releaseCamera();
}
}
4.实现点击屏幕时对焦
实现点击屏幕对焦需要我们先判断相机是否支持自动对焦,然后重写onTouch方法并在手指按下的时候执行Camera的对焦方法:
private boolean isSupportAutoFocus;//是否支持自动对焦
private void init() {
//获取屏幕分辨率
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
screenWidth = dm.heightPixels;
screenHeight = dm.widthPixels;
//判断是否支持自动对焦
isSupportAutoFocus = getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_CAMERA_AUTOFOCUS);
getHolder().addCallback(this);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN){
if(mCamera!=null&&isPreviewing){
if(isSupportAutoFocus){//支持自动对焦的话就进行对焦
mCamera.autoFocus(null);
}
}
return true;
}
return super.onTouchEvent(event);
}
5.实现拍照并获取图像
我们首先定义一个接口用于在Activity中调用拍照方法的时候返回图像。
public interface TakePictureCallBack{
/**
* 拍照的回调
* @param isSuccess
* @param img
*/
void onPictureTaken(boolean isSuccess, Bitmap img);
}
然后给TakePictureCameraView添加属性:
private TakePictureCallBack takePictureCallBack;//拍照的回调函数
接下来就是实现拍照方法,该方法首先让相机进行对焦然后通过获取对焦后的图像实现拍照,最后将拍照产生的数据通过BitmapFactory转化为Bitmap对象,当然最后产生的图像也是有90度的角度偏移的,这里我们不做处理因为我们拍身份证的时候是横着拍的刚好产生的图像不进行处理就相当于自动校正了。
/**
* 拍照
*/
public void takePicture(){
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean flag, Camera camera) {
camera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);//将字节转为Bitmap
boolean success = false;
if (bitmap != null) {
success = true;
}
stopPreview();//拍完照停止预览
if (takePictureCallBack!=null) {
takePictureCallBack.onPictureTaken(success, bitmap);
}
}
});
}
});
}
网友评论