标签:
android 开发中经常遇到拍照的需求,android 系统帮我们把相机封装成了Camera类,除了Camera还有个SurfaceView 需要用到,核心的就这2个。
# 先说下简单实现,在说里面的坑
一般实现是写个自定义view 例如(CameraView)继承SurfaceView在View 的构造方法中完成相机的初始化 重要的函数 就是 Camera.open() 和 Camera.open(i); 前一个打开时直接打开后置摄像头,后面的打开摄像头方法可以选择 打开具体的摄像头 (前置或后置) 代码实现如下:
/**
* 根据当前照相机状态(前置或后置),打开对应相机
*/
private boolean openCamera() {
if (mCamera != null) {
mCamera .stopPreview();
mCamera .release();
mCamera = null;
}
if (mIsFrontCamera) {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for ( int i = 0 ; i < Camera.getNumberOfCameras (); i++) {
Camera.getCameraInfo(i, cameraInfo);
if (cameraInfo. facing == Camera.CameraInfo.CAMERA_FACING_FRONT ) {
try {
mCamera = Camera. open(i) ;
} catch (Exception e) {
mCamera = null;
return false;
}
}
}
} else {
try {
mCamera = Camera.open();
} catch (Exception e) {
mCamera = null;
return false;
}
}
return true;
}
这里多说一句,因为系统相机这个资源全局是只有一个的,如果正在使用,就会进入锁定状态,这个时候想重新打开就必须先释放。其他代码都很简单
第二步 就是把SurfaceView 和Camera 关联起来了
使用SurfaceView 的时候需要设置一个SurfaceHolder.Callback(不清楚可以去看下SurfaceView的用法) 然后再SurfaceView 创建时候
getHolder().addCallback(callback);
这个callback 就是surfaceView的一些生命周期方法了,为什么要像这样这么麻烦的关联起来,也是和SurfaceView 特性有关了,这里也不多说了。
Callback的代码 实现如下:
private SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
if (mCamera == null) {
openCamera();
}
setCameraParameters();
mCamera .setPreviewDisplay(getHolder());
} catch (Exception e) {
}
if (mCamera != null) {
mCamera .startPreview();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
updateCameraOrientation();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//停止录像
if (mCamera != null) {
mCamera .stopPreview();
mCamera .release();
mCamera = null;
}
}
};
surfaceCreated 主要是设置参数 预览等,surfaceChanged回调里面处理旋转拍照的情况 (这个后面再说),surfaceDestroyed 就是释放资源了。
写到这一步相机差不多就可以预览了,就还差个拍照方法
public void takePicture(final Camera.PictureCallback callback, TakePictureListener listener) {
if (mCamera == null) return;
mCamera .autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (mCamera != null) {
mCamera.takePicture( null, null, callback) ;
}
}
});
}
这里加了段拍照之前先对焦一次的逻辑。 ok,到这里核心代码差不多写完了。 下面说下其中的坑:
- 相机的权限 android.permission.CAMERA android 4.0 之后相机权限属于运行时权限,所以即使声明了权限也可能用户主动拒绝,导致Camera.open(); 失败 (所以这里还有个坑,打开相机失败是用户主动拒绝了权限,还是相机被占用或者其他原因失败 区分不出来)
- 相机预览时候图片变形 这里就是 Camera.Parameters parameters = mCamera .getParameters(); 相机参数设置原因了 只有当SufaceView的(当前控件)宽高比和相机预览设置的宽高比还有生成照片的宽高比一直时预览才不会变形 代码如下:
/**
* 设置照相机参数
*/
private void setCameraParameters() {
if (mCamera == null) return;
Camera.Parameters parameters = mCamera.getParameters();
// 选择合适的预览尺寸
List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();
Collections. sort(sizeList , new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size lhs, Camera.Size rhs) {
return rhs. height - lhs.height ;
}
});
Camera.Size tempSize = null;
for (Camera.Size size : sizeList) {
if (size.width * ScreenUtil. getScreenWidth(getContext()) == ScreenUtil.getScreenHeight(getContext()) * size. height) {
tempSize = size;// parameters.setPreviewSize(size.width, size.height);
break;
}
}
if (tempSize == null) {
for (Camera.Size size : sizeList) {
//小于100W像素
if (size. width * 16 == 9 * size. height) {
tempSize = size ;// parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null) {
for (Camera.Size size : sizeList) {
//小于100W像素
if (size. width * 9 == 16 * size. height) {
tempSize = size ;// parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null) {
tempSize = sizeList.get(0) ;
}
parameters.setPreviewSize(tempSize.width , tempSize.height );
tempSize = null;
//设置生成的图片大小
sizeList = parameters.getSupportedPictureSizes() ;
Collections. sort(sizeList , new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size lhs, Camera.Size rhs) {
return rhs. height - lhs.height ;
}
});
if (sizeList.size() > 0) {
for (Camera.Size size : sizeList) {
//小于100W像素
if (size. width * ScreenUtil.getScreenWidth (getContext()) == ScreenUtil.getScreenHeight(getContext()) * size. height) {
tempSize = size ;// parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null) {
for (Camera.Size size : sizeList) {
//小于100W像素
if (size. width * 16 == 9 * size. height) {
tempSize = size ;// parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null) {
for (Camera.Size size : sizeList) {
//小于100W像素
if (size. width * 9 == 16 * size. height) {
tempSize = size ;// parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null) {
tempSize = sizeList.get(0) ;
}
parameters.setPictureSize(tempSize.width , tempSize.height );
if (parameters.getSupportedFocusModes().contains(Camera.Parameters. FOCUS_MODE_CONTINUOUS_PICTURE )) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) ;
}
// mCamera .cancelAutoFocus();//只有加上了这一句,才会自动对焦。 然并卵!
// //设置图片格式
parameters.setPictureFormat(ImageFormat. JPEG);
parameters.setJpegQuality( 100);
parameters.setJpegThumbnailQuality( 100);
//自动聚焦模式
parameters.setFocusMode(Camera.Parameters. FOCUS_MODE_AUTO);
mCamera .setParameters(parameters);
//开启屏幕朝向监听
startOrientationChangeListener() ;
}
这里建议尺寸就和屏幕大小一样的 如果产品需求的预览范围比较小,就用其他view 去遮挡其他区域(这么做会埋下另外一个坑,但是基本所有android都能适配了)。
- 第三坑 预览时候手机横着或者旋转 发现预览的图片不对 。这里需要动态设置相机的预览参数,也就是前面SurfaceHolder.Callback里面surfaceChanged改变时候处理的updateCameraOrientation();代码实现如下:
/**
* 根据当前朝向修改保存图片的旋转角度
*/
private void updateCameraOrientation() {
if (mCamera != null) {
Camera.Parameters parameters = mCamera .getParameters();
//rotation参数为 0、90、180、270。水平方向为0。
int rotation = 90 + mOrientation == 360 ? 0 : 90 + mOrientation ;
//前置摄像头需要对垂直方向做变换,否则照片是颠倒的
if (mIsFrontCamera) {
if (rotation == 90) rotation = 270;
else if (rotation == 270) rotation = 90 ;
}
Log.e( "TAG", "rotation=" + rotation + "mOrientation=" + mOrientation);
parameters.setRotation(rotation) ;//生成的图片转90°
//预览图片旋转90°
mCamera .setDisplayOrientation(90) ;//预览转90°
mCamera .setParameters(parameters);
}
}
需要根据前置和后置摄像头来区分 ,还要监听屏幕朝向来设置方向 (主要用于横竖屏切换 如果没这个需求 可以不加)
/**
* 启动屏幕朝向改变监听函数 用于在屏幕横竖屏切换时改变保存的图片的方向
*/
private void startOrientationChangeListener() {
OrientationEventListener mOrEventListener = new OrientationEventListener(getContext()) {
@Override
public void onOrientationChanged(int rotation) {
if (((rotation >= 0) && (rotation <= 45)) || (rotation > 315 )) {
rotation = 0;
} else if ((rotation > 45 ) && (rotation <= 135)) {
rotation = 90;
} else if ((rotation > 135 ) && (rotation <= 225)) {
rotation = 180;
} else if ((rotation > 225 ) && (rotation <= 315)) {
rotation = 270;
} else {
rotation = 0;
}
if (rotation == mOrientation)
return;
mOrientation = rotation;
updateCameraOrientation() ;
}
};
mOrEventListener.enable() ;
}
- 三星手机拍照 旋转了90度问题 。 三星手机部分机型摄像头默认是横着的,解决方法是记录下拍照时候的角度,然后生成照片后比较下照片的长和宽,如果和预计的不一样就像左或右旋转90度(根据拍照时候的角度选择左右)。
到这里相机基本功能差不多就完成了,还有很多其他的坑没说到,但都是比较好解决了。还有就是对焦很缩放 闪光灯等功能 ,下篇文章再说吧。
Android 相机开发中的坑
标签:
原文地址:http://www.cnblogs.com/pro-huan/p/5786919.html