前一阵子负责一个自定义相机进行拍照并在另一个页面进行人脸识别的模块,相机部分需求并不怎么复杂,可以切换前后摄像头,可以拍照并把照片返回上一个页面。由于没有怎么接触过自定义相机的部分,而网上的一些资料又不全,踩了不少坑。故在这里总结一下,希望对大家有所帮助,同时把自定义控件系列的最后一个坑填上(surfaceview)。效果图如下:
Android系统提供了两种使用手机相机资源实现拍摄功能的方法,一种是直接通过Intent调用系统相机组件,这种方法快速方便,适用于直接获得照片的场景,如上传相册,微博、朋友圈发照片等。另一种是使用相机API来定制自定义相机,这种方法适用于需要定制相机界面或者开发特殊相机功能的场景,如需要对照片做裁剪、滤镜处理,添加贴纸,表情,地点标签等。
关于系统自带相机的调用非常简单,这里我就不过多叙述了,具体可以参考谷歌的Training。我只说容易被大家忽视的几个点:
如果我们的应用使用相机,但相机并不是应用的正常运行所必不可少的组件,可以将权限声明中的android:required设置为”false”。这样的话,Google Play 也会允许没有相机的设备下载该应用。当然我们有必要在使用相机之前通过调用hasSystemFeature(PackageManager.FEATURE_CAMERA)方法来检查设备上是否有相机。如果没有,我们应该禁用和相机相关的功能!
在调用startActivityForResult()方法之前,先调用resolveActivity(),这个方法会返回能处理该Intent的第一个Activity(译注:即检查有没有能处理这个Intent的Activity)。执行这个检查非常重要,因为如果在调用startActivityForResult()时,没有应用能处理你的Intent,应用将会崩溃。所以只要返回结果不为null,使用该Intent就是安全的。
使用API来控制相机我们需要用到关键类和接口:
接下来我们分为以下三部分来介绍:关键类以及接口的作用和方法,Camera控制拍照步骤,自定义相机容易踩到的坑以及解决办法。
Camera :最主要的类,用于管理和操作camera资源。它提供了完整的相机底层接口,支持相机资源切换,设置预览/拍摄尺寸,设定光圈、曝光、聚焦等相关参数,获取预览/拍摄帧数据等功能,主要方法有以下这些:
SurfaceView :用于绘制相机预览图像的类,提供给用户实时的预览图像。普通的view以及派生类都是共享同一个surface的,所有的绘制都必须在UI线程中进行。而surfaceview是一种比较特殊的view,它并不与其他普通view共享surface,而是在内部持有了一个独立的surface,surfaceview负责管理这个surface的格式、尺寸以及显示位置。由于UI线程还要同时处理其他交互逻辑,因此对view的更新速度和帧率无法保证,而surfaceview由于持有一个独立的surface,因而可以在独立的线程中进行绘制,因此可以提供更高的帧率。自定义相机的预览图像由于对更新速度和帧率要求比较高,所以比较适合用surfaceview来显示。
SurfaceHolder :surfaceholder是控制surface的一个抽象接口,它能够控制surface的尺寸和格式,修改surface的像素,监视surface的变化等等,surfaceholder的典型应用就是用于surfaceview中。surfaceview通过getHolder()方法获得surfaceholder 实例,通过后者管理监听surface 的状态。
SurfaceHolder.Callback 接口 :负责监听surface状态变化的接口,有三个方法:
具体实现代码戳后面链接
先看下官方文档的说明
Most camera applications lock the display into landscape mode because that is the natural orientation of the camera sensor. This setting does not prevent you from taking portrait-mode photos, because the orientation of the device is recorded in the EXIF header. The setCameraDisplayOrientation() method lets you change how the preview is displayed without affecting how the image is recorded. However, in Android prior to API level 14, you must stop your preview before changing the orientation and then restart it.
大多数相机程序会锁定预览为横屏状态,因为该方向是相机传感器的自然方向。当然这一设定并不会阻止我们去拍竖屏的照片,因为设备的方向信息会被记录在EXIF头中。setCameraDisplayOrientation()方法可以让你在不影响照片拍摄过程的情况下,改变预览的方向。然而,对于Android API Level 14及以下版本的系统,在改变方向之前,我们必须先停止预览,然后再去重启它。
说明这个问题之前,同样先说一下几个跟相机有关的尺寸。
SurfaceView尺寸 :即自定义相机应用中用于显示相机预览图像的View的尺寸,当它铺满全屏时就是屏幕的大小。这里surfaceview显示的预览图像暂且称作手机预览图像。
Previewsize :相机硬件提供的预览帧数据尺寸。预览帧数据传递给SurfaceView,实现预览图像的显示。这里预览帧数据对应的预览图像暂且称作相机预览图像。
Picturesize :相机硬件提供的拍摄帧数据尺寸。拍摄帧数据可以生成位图文件,最终保存成.jpg或者.png等格式的图片。这里拍摄帧数据对应的图像称作相机拍摄图像。图4说明了以上几种图像及照片之间的关系。手机预览图像是直接提供给用户看的图像,它由相机预览图像生成,拍摄照片的数据则来自于相机拍摄图像。
原因是没有正确设置比例 parameter.setPictureSize(width,height),这个比例不是你决定的,要先通过camera.getParameters().getSupportedPictureSizes()获得手机支持的尺寸。
/**
* 设置照片格式
*/
private void setParameter() {
Camera.Parameters parameters = camera.getParameters(); // 获取各项参数
parameters.setPictureFormat(PixelFormat.JPEG); // 设置图片格式
parameters.setJpegQuality(100); // 设置照片质量
//获得相机支持的照片尺寸,选择合适的尺寸
List.Size> sizes = parameters.getSupportedPictureSizes();
int maxSize = Math.max(display.getWidth(), display.getHeight());
int length = sizes.size();
if (maxSize > 0) {
for (int i = 0; i < length; i++) {
if (maxSize <= Math.max(sizes.get(i).width, sizes.get(i).height)) {
parameters.setPictureSize(sizes.get(i).width, sizes.get(i).height);
break;
}
}
}
List.Size> ShowSizes = parameters.getSupportedPreviewSizes();
int showLength = ShowSizes.size();
if (maxSize > 0) {
for (int i = 0; i < showLength; i++) {
if (maxSize <= Math.max(ShowSizes.get(i).width, ShowSizes.get(i).height)) {
parameters.setPreviewSize(ShowSizes.get(i).width, ShowSizes.get(i).height);
break;
}
}
}
camera.setParameters(parameters);
}
//将照片改为竖直方向
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
Matrix matrix = new Matrix();
switch (cameraPosition) {
case 0://前
matrix.preRotate(270);
break;
case 1:
matrix.preRotate(90);
break;
}
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
源码戳这里。