前言
由于网上大部分自定义相机的实现,都是耦合性比较强的,不方便今后的复用,所以我自己实现了一套自定义相机,方便以后的扩展。自定义相机分为以下3个部分。文末有免费福利哦
- 相机的预览布局SurfaceView ,方便用户实时预览。写成自定义控件,方便今后的复用。
- 相机的自动聚焦以及点触聚焦,拍照需要聚焦,要不然拍出的图片很可能是模糊的。写成自定义控件,方便今后的复用。
- 相机的自定义布局,这部分随着需求的迭代变换,前面的2大块不需要改动。
一、预览布局的实现
(1) 抽离预览图层为一个单独的自定义控件CameraPreview ,传递Camre对象,设置必要的相机预览参数。
package com.focustech.xyz.baselibrary.camera;
import android.app.Activity;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.focustech.xyz.baselibrary.common.XyzLogger;
import java.io.IOException;
import java.util.SortedSet;
/**
*
*/
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private Camera mCamera;
private boolean isPreview;
private Context context;
/**
* 预览尺寸集合
*/
private final SizeMap mPreviewSizes = new SizeMap();
/**
* 图片尺寸集合
*/
private final SizeMap mPictureSizes = new SizeMap();
/**
* 屏幕旋转显示角度
*/
private int mDisplayOrientation;
/**
* 设备屏宽比
*/
private AspectRatio mAspectRatio;
/**
*
* @param context
* @param mCamera
*/
public CameraPreview(Context context, Camera mCamera) {
super(context);
this.context = context;
this.mCamera = mCamera;
this.mHolder = getHolder();
this.mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mDisplayOrientation = ((Activity) context).getWindowManager().getDefaultDisplay().getRotation();
mAspectRatio = AspectRatio.of(16, 9);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
//设置设备高宽比
mAspectRatio = getDeviceAspectRatio((Activity) context);
//设置预览方向
mCamera.setDisplayOrientation(90);
Camera.Parameters parameters = mCamera.getParameters();
//获取所有支持的预览尺寸
mPreviewSizes.clear();
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
mPreviewSizes.add(new Size(size.width, size.height));
}
//获取所有支持的图片尺寸
mPictureSizes.clear();
for (Camera.Size size : parameters.getSupportedPictureSizes()) {
mPictureSizes.add(new Size(size.width, size.height));
}
Size previewSize = chooseOptimalSize(mPreviewSizes.sizes(mAspectRatio));
Size pictureSize = mPictureSizes.sizes(mAspectRatio).last();
//设置相机参数
parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
parameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
parameters.setPictureFormat(ImageFormat.JPEG);
parameters.setRotation(90);
mCamera.setParameters(parameters);
//把这个预览效果展示在SurfaceView上面
mCamera.setPreviewDisplay(holder);
//开启预览效果
mCamera.startPreview();
isPreview = true;
} catch (IOException e) {
XyzLogger.e("CameraPreview", "相机预览错误: " + e.getMessage());
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (holder.getSurface() == null) {
return;
}
//停止预览效果
mCamera.stopPreview();
//重新设置预览效果
try {
mCamera.setPreviewDisplay(mHolder);
} catch (IOException e) {
e.printStackTrace();
}
mCamera.startPreview();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCamera != null) {
if (isPreview) {
//正在预览
mCamera.stopPreview();
mCamera.release();
}
}
}
/**
* 注释:获取设备屏宽比
* 时间:2019/3/4 0004 12:55
* 作者:郭翰林
*/
private AspectRatio getDeviceAspectRatio(Activity activity) {
int width = activity.getWindow().getDecorView().getWidth();
int height = activity.getWindow().getDecorView().getHeight();
return AspectRatio.of(height, width);
}
/**
* 注释:选择合适的预览尺寸
* 时间:2019/3/4 0004 11:25
* 作者:郭翰林
*
* @param sizes
* @return
*/
@SuppressWarnings("SuspiciousNameCombination")
private Size chooseOptimalSize(SortedSet<Size> sizes) {
int desiredWidth;
int desiredHeight;
final int surfaceWidth = getWidth();
final int surfaceHeight = getHeight();
if (isLandscape(mDisplayOrientation)) {
desiredWidth = surfaceHeight;
desiredHeight = surfaceWidth;
} else {
desiredWidth = surfaceWidth;
desiredHeight = surfaceHeight;
}
Size result = null;
for (Size size : sizes) {
if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {
return size;
}
result = size;
}
return result;
}
/**
* Test if the supplied orientation is in landscape.
*
* @param orientationDegrees Orientation in degrees (0,90,180,270)
* @return True if in landscape, false if portrait
*/
private boolean isLandscape(int orientationDegrees) {
return (orientationDegrees == 90 ||
orientationDegrees == 270);
}
}
复制代码
这里有2处地方需要注意,相机要设置正确的预览尺寸和正确的图片的尺寸。如果预览尺寸设置错误,则预览布局会被拉伸或者收缩。如果图片尺寸设置错误,部分机型会导致闪退或者拍出的照片很不清晰。
这里适配预览尺寸和图片尺寸,是根据设备的屏宽比和Carme拿到的说支持的预览尺寸和图片尺寸计算得出应有的预览尺寸和图片尺寸,代码如下。文末有免费福利哦
//设置设备高宽比
mAspectRatio = getDeviceAspectRatio((Activity) context);.
Camera.Parameters parameters = mCamera.getParameters();
//获取所有支持的预览尺寸
mPreviewSizes.clear();
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
mPreviewSizes.add(new Size(size.width, size.height));
}
//获取所有支持的图片尺寸
mPictureSizes.clear();
for (Camera.Size size : parameters.getSupportedPictureSizes()) {
mPictureSizes.add(new Size(size.width, size.height));
}
Size previewSize = chooseOptimalSize(mPreviewSizes.sizes(mAspectRatio));
Size pictureSize = mPictureSizes.sizes(mAspectRatio).last();
//设置相机参数
parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
parameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
复制代码
/**
*
*/
private AspectRatio getDeviceAspectRatio(Activity activity) {
int width = activity.getWindow().getDecorView().getWidth();
int height = activity.getWindow().getDecorView().getHeight();
return AspectRatio.of(height, width);
}
复制代码
/**
*
*
* @param sizes
* @return
*/
@SuppressWarnings("SuspiciousNameCombination")
private Size chooseOptimalSize(SortedSet<Size> sizes) {
int desiredWidth;
int desiredHeight;
final int surfaceWidth = getWidth();
final int surfaceHeight = getHeight();
if (isLandscape(mDisplayOrientation)) {
desiredWidth = surfaceHeight;
desiredHeight = surfaceWidth;
} else {
desiredWidth = surfaceWidth;
desiredHeight = surfaceHeight;
}
Size result = null;
for (Size size : sizes) {
if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {
return size;
}
result = size;
}
return result;
}
复制代码
(2)屏宽比AspectRatio的实现
package com.focustech.xyz.baselibrary.camera;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.v4.util.SparseArrayCompat;
/**
*
*/
public class AspectRatio implements Comparable<AspectRatio>, Parcelable {
private final static SparseArrayCompat<SparseArrayCompat<AspectRatio>> sCache
= new SparseArrayCompat<>(16);
private final int mX;
private final int mY;
/**
*