Android中开发相机的两种方式:
Android系统提供了两种使用手机相机资源实现拍摄功能的方法,一种是直接通过Intent调用系统相机组件,这种方法快速方便,适用于直接获得照片的场景,如上传相册,微博、朋友圈发照片等。另一种是使用相机API来定制自定义符合自己需求的相机,这种方法适用于需要定制相机界面或者开发特殊相机功能的场景,如需要对照片做裁剪、滤镜处理,添加贴纸,表情,地点标签等。(我在使用的时候发现Camera已经被摒弃了,API 21中出现了camera2这个类来代替camera类,但是笔者的测试手机还是andorid 4.4,所以还是用Camera)
这里我就简单介绍一下我是怎么实现自定义相机的,虽然界面效果有点low,这里主要介绍一下功能,首先我们需要配置相机权限,由于我这里是将拍照的图片存储在文件夹里面的所以还需要读写文件的权限(权限如下)
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission><uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.CAMERA" /><uses-feature android:name="android.hardware.camera.autofocus" /><uses-feature android:name="android.hardware.camera" />
那么接下来,使用相机我们总需要一个能够看到图像的地方吧,这里Google叫我们使用SurfaceView这个类,那么SurfaceView这个类 是什么呢,首先这个类是继承View的,可以在将图像绘制在屏幕上并显示给用户。其实能够显示的原因是SurfaceView中包含一个Surface对 象,Surface是SurfaceView的可见部分.这里就不详细介绍了. 对于自定义相机我们需要考虑传感器 以及相机的分辨率 预览图片的分辨率 在这里我暂时定义了三个传感器(加速度 ,旋转矢量,陀螺仪)部分代码如下:
public class SensorUtil {//定义3种传感器的数组,依次为加速度>旋转矢量>陀螺仪 private static int[] mSensorTypes = {Sensor.TYPE_ACCELEROMETER , Sensor.TYPE_ROTATION_VECTOR, Sensor.TYPE_GYROSCOPE}; //是否使用传感器或传感器是否可以使用 private static boolean mIsStart = false; // 定义长度为3的数组保存传感器上一次记录的数据,0,1,2分别对应x,y,z轴数据 // 初始化记录数据 private static float[] mLastValues = {0f, 0f, 0f}; //定义阈值变化的范围 //定义加速度限定的值 public final static float LIMIT_ACCELEROMETER = 1.0f; //定义旋转矢量限定值 public final static float LIMIT_ROTATION = 0.8f; //定义陀螺仪限定值 public final static float LIMIT_GYROSCOPE = 0.8f; /** * @return void 返回类型 * @throws * @Title: restartSensor * @param sensorManager 传感器管理器 * @param listener A {@link SensorEventListener SensorEventListener} object * @author: */ public static void startSensor(SensorManager sensorManager, SensorEventListener listener) {// 获取当前机器支持的最优的传感器Sensor sensor =getBestSensor(sensorManager);// 表示未获取到所需的传感器 if (null == sensor) {Log.d("Sensor", "系统不存在所需的传感器,开启定时器聚焦模式"); }else{mIsStart=true; /*** * 注册传感器监听事件* * this,表示SensorEventListener* * sensor,表示对应的传感器* * SensorManager.SENSOR_DELAY_NORMAL,表示传感器的刷新频率* */ sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_GAME);Log.i("Sensor", "注册传感器");}}/** * 得到所需的最优的传感器,如果没有下面3个即返回null * 权重比较:旋转矢量>陀螺仪>加速度 * @Title: getBestSensor * @Description:* @author * @date * @param sensorMag,系统的传感器管理器 * @return Sensor */ public static Sensor getBestSensor(SensorManager sensorMag){mIsStart=false;//遍历需要用到的3种传感器for (int i=0; i<mSensorTypes.length; i++){Sensor sensor = sensorMag.getDefaultSensor(mSensorTypes[i]); //获取到存在的传感器返回 if (sensor!= null){return sensor; }}//为找到符合条件的传感器return null; }/** * 返回是否注册了传感器 * @Title: isSensor * @Description: * @author * @date * @return boolean */ public static boolean isStart(){return mIsStart;}public static void setIsStart(boolean isStart){mIsStart=isStart;}/** * 比较传感器数据变化是否在可接受的范围内 * @Title: isOverRange * @Description:* @author * @date * @param event,当前传回数据的传感器事件 * @return * @return boolean * @throws */ public static boolean isOverRange(SensorEvent event){boolean ok = false;//根据不同的传感器进行不同的判断switch (event.sensor.getType()){//旋转矢量传感器case Sensor.TYPE_ROTATION_VECTOR:ok = compareRotaion(event.values); break;//陀螺仪传感器case Sensor.TYPE_GYROSCOPE:ok = compareGyroscope(event.values); break;//加速度传感器case Sensor.TYPE_ACCELEROMETER:ok = compareAccelerometer(event.values); break;default:break;}// 保存当前的值用于比对if (ok) {mLastValues[0] = event.values[0]; mLastValues[1] = event.values[1]; mLastValues[2] = event.values[2];}return ok; }/** * 旋转矢量比较 * @Title: compareRotaion * @Description:* @author * @date * @param values,当前的数据 * @return * @return boolean * @throws */ private static boolean compareRotaion( float[] values){//比较两次变化的差异值 float deltaX = Math.abs(values[0] - mLastValues[0]); float deltaY = Math.abs(values[1] - mLastValues[1]); float deltaZ = Math.abs(values[2] - mLastValues[2]); //根据差异值判断是否超过范围if (deltaX > SensorUtil.LIMIT_ROTATION || deltaY > SensorUtil.LIMIT_ROTATION || deltaZ > SensorUtil.LIMIT_ROTATION){Log.i("haha", ">>>>>overRange"); return true;}return false; }/** * 陀螺仪比较 * @Title: compareGyroscope * @Description:* @author * @date * @param values,当前数据 * @return * @return boolean * @throws */ private static boolean compareGyroscope( float[] values){//比较两次变化的差异值 float delateX = Math.abs(values[0] - mLastValues[0]); float delateY = Math.abs(values[1] - mLastValues[1]); float delateZ = Math.abs(values[2] - mLastValues[2]); //根据差异值判断是否在阈值范围类if (delateX > SensorUtil.LIMIT_GYROSCOPE || delateY > SensorUtil.LIMIT_GYROSCOPE || delateZ > SensorUtil.LIMIT_GYROSCOPE){return true;}return false; }/** * 加速度比较 * @Title: compareGyroscope * @Description:* @author * @date * @param values,当前数据 * @return * @return boolean * @throws */ private static boolean compareAccelerometer(float[] values){//比较两次变化的差异值 float delateX = Math.abs(values[0] - mLastValues[0]); float delateY = Math.abs(values[1] - mLastValues[1]); float delateZ = Math.abs(values[2] - mLastValues[2]); //通过差异值判断是否在阈值内if (delateX > SensorUtil.LIMIT_ACCELEROMETER || delateY > SensorUtil.LIMIT_ACCELEROMETER || delateZ > SensorUtil.LIMIT_ACCELEROMETER){return true;}return false; }}
自定义相机拍照快门声音(部分代码如下):
public classSoundUtils {
//定义左右声道的音量大小 public final static float LEFT_VOLUME = 1.0f; public final static float RIGHT_VOLUME = 1.0f; /**** @Title: playerScanOkWav* @Description: R.raw.scan_ok* @author* @date 2* @param @param context 设定文件* @param type 0:扫描;1:拍照* @return void 返回类型* @throws*/ public final static void playerScanOkWav(Context context, int type){int sound =R.raw.scan_ok; if(type == 1){sound = R.raw.camera; }MediaPlayer mediaPlayer = MediaPlayer.create(context,sound); mediaPlayer.setVolume(LEFT_VOLUME, RIGHT_VOLUME); mediaPlayer.start(); }}
点击拍照按钮时进入预览图像界面需要 打开相机调用Camera.open()的方法:
/***@date 创建时间 /4/15 *@author *@company *@name:zhongshuiping *@Description 打开相机 */public Camera getCameraInstance() {Camera c = null; try {c = Camera.open(); // 打开相机异常 } catch (Exception e) {}return c;}
当我们拍照完成结束界面时需要启动关闭相机的功能:
/** *@date 创建时间 /4/15 *@author *@company *@name:zhongshuiping *@Description关闭相机 */@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {if (flashlight) {if (mCamera != null) {Parameters params = mCamera.getParameters(); // 关闭闪光灯 params.setFlashMode(Parameters.FLASH_MODE_OFF); }flashlightBtn.setBackgroundResource(R.drawable.flashlightclose); flashlight = false; }if (mCamera != null) {mCamera.setPreviewCallback(null); mCamera.stopPreview(); mCamera.release(); mCamera = null; }// 判断是否开启传感器监听并注销监听 SensorCancellation();}
传感器改变事件:
/** *@date 创建时间 /4/15 *@author *@company *@name:zhongshuiping *@Description 传感器精度改变事件 */@Overridepublic void onAccuracyChanged(Sensor arg0, int arg1) {}
/***@date 创建时间 /4/15*@author*@company*@name:zhongshuiping*@Description 传感器改变事件*/ @Override public void onSensorChanged(SensorEvent event) {// 判断相机是否准备好并且手机移动超过一定的范围 if (mCamera != null && SensorUtil.isStart() && SensorUtil.isOverRange(event)&&!bIsFocusing) {// 调用自动聚焦回调 bIsFocus = false; bIsFocusing = true; finder_view.bFocused = false;// watermark.setVisibility(View.VISIBLE); finder_view.invalidate(); //Log.i(TAG, "==================================onSensorChanged bIsFocus = false"); mCamera.cancelAutoFocus();// 只有加上了这一句,才会自动对焦 mCamera.autoFocus(autoFocusCB); }}
/** *@date 创建时间 /4/15 *@author *@company *@name:zhongshuiping *@Description 传感器注销事件 */private void SensorCancellation(){if (SensorUtil.isStart()) {SensorUtil.setIsStart(false); sensorMag.unregisterListener(PhotographActivity.this); }}
匹配图片分辨率这里我循环查找该手机所以快高分辨率比由高往低查找直到找到16/9的比例的分辨率由于我这里的对焦框是固定的图片对焦框比例是按照16/9的比例绘制的:
/** * 设定的屏幕的比例不是图片的比例 *@date 创建时间 /4/15 *@author *@company *@name:zhongshuiping *@Description 匹配分辨率 */private Size getOptimalPreviewSize(List<Size> sizes, double targetRatio) {if (sizes == null)return null; Size optimalSize = null; Collections.sort(sizes, new Comparator<Size>() {@Override public int compare(Size lhs, Size rhs) {return new Double(lhs.width).compareTo(new Double(rhs.width)); }}); for (int i=sizes.size()-1;i>=0;i--) {Size size = sizes.get(i); if ((( Constants.EIGHT_HUNDRED < size.width && size.width < Constants.TWO_THOUSAND)|| (Constants.EIGHT_HUNDRED< size.height && size.height < Constants.TWO_THOUSAND))&& ((size.width * 9) == (size.height * 16) )) {optimalSize = size; break; }}return optimalSize;}
/** * 设置的是拍照的图片的比例 *@date 创建时间 /4/15 *@author *@company *@name:zhongshuiping *@Description 匹配分辨率 */private Size getOptimalPictureSize(List<Size> sizes, double targetRatio) {if (sizes == null)return null; Size optimalSize = null; Collections.sort(sizes, new Comparator<Size>() {@Override public int compare(Size lhs, Size rhs) {return new Double(lhs.width).compareTo(new Double(rhs.width)); }}); for (int i=sizes.size()-1;i>=0;i--) {Size size = sizes.get(i); if (((Constants.NUMBER_ONE_THOUSAN < size.width && size.width < Constants.NUMBER_TWO_THOUSAND)|| (Constants.NUMBER_ONE_THOUSAN < size.height && size.height < Constants.NUMBER_TWO_THOUSAND))&& ((size.width * 9) ==(size.height * 16) )) {optimalSize = size; break; }}/**如果没找到16/9的就选择最接近的*/ if(optimalSize == null){double dMin = 100.0; Size RightSize = null; for (Size size : sizes) {double fRate = size.width/(float)size.height; double fDistance = Math.abs(fRate - 16.0/9.0); //找最接近16比9的size; if(fDistance < dMin){dMin = fDistance;RightSize = size; }}//最接近的值赋给变量optimalSize optimalSize = RightSize; }return optimalSize;}
对图片进行处理我们需要时需要释放资源:
/** *@date 创建时间 /4/15 *@author *@company*@name:zhongshuiping *@Description Activity被暂停或收回cpu和其他资源时调用时调stopPreview释放资源 */public void onPause() {super.onPause(); sensorMag.unregisterListener(this); stopPreview();}
/** *@date 创建时间 /4/15 *@author *@company *@name:zhongshuiping *@Description 释放资源 */private void stopPreview() {if (mCamera != null) {try {mCamera.setPreviewDisplay(null); mCamera.stopPreview(); } catch (Exception e) {}}}
在拍照场景不是很理想的情况小我们需要打开闪光灯,辅助我们达到一个比较理想的拍照场景
/** *@date 创建时间 /4/15 *@author *@company *@name:zhongshuiping *@Description 闪光灯操作 */private void openFlashLamp(){if (mCamera == null) {mCamera = getCameraInstance(); }Parameters params = mCamera.getParameters(); /*** 闪光灯*/ if (flashlight) {// 关闭闪光灯 params.setFlashMode(Parameters.FLASH_MODE_OFF); flashlightBtn.setBackgroundResource(R.drawable.zx_code_closelight); flashlight = false; } else {// 打开闪光灯 params.setFlashMode(Parameters.FLASH_MODE_TORCH); flashlightBtn.setBackgroundResource(R.drawable.zx_code_openlight); flashlight = true; }mCamera.setParameters(params);}
相机的聚焦是通过传感器来时时聚焦的 这里我设置了可手动聚焦 通过点击屏幕聚焦,这里的聚焦是全屏聚焦跟原生相机不一样,原生相机是触碰点附近聚焦:
/***@date 创建时间 /4/15*@author*@company*@name:zhongshuiping*@Description AutoFocusCallback自动对焦*/ AutoFocusCallback autoFocusCB = new AutoFocusCallback() {public void onAutoFocus(boolean success, Camera camera) {bIsFocusing = false; if (success) {bIsFocus = true;finder_view.bFocused = true;//watermark.setVisibility(View.GONE);finder_view.invalidate(); } else {bIsFocus = false;finder_view.bFocused = false;//watermark.setVisibility(View.VISIBLE);finder_view.invalidate(); }}};
以上是自定义相机的部分代码详细代码如下地址:
/download/androidzsp/10024882
如果觉得《android自定义相机拍照》对你有帮助,请点赞、收藏,并留下你的观点哦!