老规矩,先上一下项目地址:GitHub:https://github.com/xiangzhihong/CameraDemo
方式:
- 调用Camera API 自定义相机
- 调用系统相机
由于需求不同,所以选择的方案固然也不同,至于第二种调用系统相机,这里就不过多讲解了,使用Intent对象设置一个Action动作即可,跳转时使用startActivityForResult,然后在onActivityResult处理相关数据便可,关键代码:
- intent.setAction("android.media.action.STILL_IMAGE_CAMERA");
至于使用,较常见的一般是应用中用户上传头像的时候调用,然后返回处理图像数据。
而第一种自定义相机的方式使用也十分普遍,但是要做好这个模块,相对来说还是有一定难度的,之前分享过一个Github上的开源相机的项目,项目由美国的一个团队开发,集 拍照、摄影、各种特效动画 等功能与一身,本人之前研究了下,发现功能比较全面也很强大,抠出来单独拍照那一个模块,我滴妈呀,真TM费劲!相机不管是预览还是拍摄图像都还是很清晰的,自己当时也写了一个,比较操蛋,只能怪自己对这一块的优化了解浅显吧!特别是预览的时候,聚焦完成后,焦点周边会出现很多白色的噪点,密密麻麻,特别严重,头疼的很。不过也总算解决了,灰常感谢USA的那个什么什么团队的开源相机程序。经过自己改造后的预览效果图:
下面看下这个项目的效果图,我也把地址甩底下,大伙感兴趣的自行Clone研究(或者闲的蛋疼也可以抽时间剥离开每一个模块学习,作为日后的知识储备),里面也用到了这个Android中读取图片EXIF元数据之metadata-extractor的使用。
相机开发简介
下面说说在Android中调用Camera来定义相机的最基本步骤:
- 打开相机 —— 调用Camera的open()方法。
- 获取拍照参数 —— 调用Camera的getParameters()方法,返回Camera.Parameters对象。
- 拍照参数设置 —— 调用Camera.Parameters对象。
- 拍照参数控制 —— 调用Camera的setParameters(),并将Camera.Parameters对象作为参数传入。注:Android2.3.3之后不用设置。
- 预览取景 —— 调用Camera的startPreview()方法,在之前注意调用Camera的setPreviewDisplay(SurfaceHolder holder)设置使用哪个SurfaceView来显示取得的图片。
- 拍照 —— 调用Camera的takePicture()
- 停止预览 —— 调用Camera的stopPreview()方法
- 资源释放 —— Camera.release()
开启和关闭预览的联系如下:Camera ---- SurfaceHolder ------ SurfaceView
关于SurfaceHolder.Callback必须实现的3个方法:
surfaceCreated() 该方法在surfaceView被Create时调用 surfaceChanged() 该方法是当surfaceView发生改变后调用 surfaceDestroyed() 这个不用说了,销毁时调用 surfaceHolder通过addCallBack()方法将响应的接口绑定
注:必要Camera权限,例如:
关于Camera下的Parameters类,其中封装了我们需要的大部分功能,下面做个简单介绍:
- setPictureFormat() 方法用于设置相机照片的格式,其参数是一个字符型参数,位于PixelFormat类中,如:PixelFormat.JPEG。
- setSceneMode() 方法用于设置相机场景类型,其参是是一个字符型参数,位于Parameters类中,以SCENE_MODE_开头。
- setZoom() 方法用于设置相机焦距,其参数是一个整型的参数,该参数的范围是0到Camera.getParameters().getMaxZoom()。
- setPictureSize() 方法用于设置相机照片的大小,参数为整型。
- setWhiteBalance() 方法用于设置相机照片白平衡,其参数是一个字符型,位于Parameters类中,以WHITE_BALANCE开头。
- setJpegQuality() 方法用于设置相机照片的质量,其参数是一个整型参数,取值范围为1到100。
- setFlashMode() 方法用于设置闪光灯的类型,其参数是一个字符型参数,位于Parameters类中,以FLASH_MODE_开头。
- setColorEffect() 方法用于设置照片颜色特效的类型,其参数是一个字符型参数,位于Parameters类中,以EFFECT_开头。
本程序模块效果图及示例
下面分享本篇Blog的示例相机模块,此功能模块并非上面开源项目中的剥离出来的,看下效果图咯:
效果看着还可以吧(不点赞也太不给面子了吧 - . - ),下面个出主界面的布局代码:
下面是核心模块 CameraPreview 类:
- public class CameraPreview extends ViewGroup implements SurfaceHolder.Callback, Camera.AutoFocusCallback {
- private SurfaceView mSurfaceView;
- private SurfaceHolder mHolder;
- private Size mPreviewSize;
- private Size adapterSize;
- //private List mSupportedPreviewSizes;
- private Camera mCamera;
- private boolean isSupportAutoFocus = false;
- private Camera.Parameters parameters = null;
- private Context mContext;
- //private int mCurrentCameraId = 0;
- private int screenWidth;
- private int screenHeight;
- CameraPreview(Context context, SurfaceView sv) {
- super(context);
- mContext = context;
- mSurfaceView = sv;
- mHolder = mSurfaceView.getHolder();
- mHolder.addCallback(this);
- mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
- mHolder.setKeepScreenOn(true);
- isSupportAutoFocus = context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_CAMERA_AUTOFOCUS);
- DisplayMetrics dm = new DisplayMetrics();
- ((Activity) mContext).getWindowManager().getDefaultDisplay().getMetrics(dm);
- screenWidth = dm.widthPixels;
- screenHeight = dm.heightPixels;
- }
- public void setCamera(Camera camera) {
- mCamera = camera;
- initCamera();
- }
- public void initCamera() {
- if (mCamera != null) {
- Camera.Parameters params = mCamera.getParameters();
- //mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
- requestLayout();
- if (mPreviewSize == null) {
- mPreviewSize = findBestPreviewResolution();
- }
- if (adapterSize == null) {
- adapterSize = findBestPictureResolution();
- }
- if (adapterSize != null) {
- params.setPictureSize(adapterSize.width, adapterSize.height);
- }
- if (mPreviewSize != null) {
- params.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
- }
- params.setPictureFormat(PixelFormat.JPEG);
- List focusModes = params.getSupportedFocusModes();
- if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
- // set the focus mode
- params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
- // set Camera parameters
- mCamera.setParameters(params);
- }
- setDispaly(params, mCamera);
- //setCameraDisplayOrientation((Activity) mContext, mCurrentCameraId, mCamera);
- mCamera.setParameters(params);
- }
- }
- //控制图像的正确显示方向
- private void setDispaly(Camera.Parameters parameters, Camera camera) {
- if (Build.VERSION.SDK_INT >= 8) {
- setDisplayOrientation(camera, 90);
- } else {
- parameters.setRotation(90);
- }
- }
- //实现的图像的正确显示
- private void setDisplayOrientation(Camera camera, int i) {
- Method downPolymorphic;
- try {
- downPolymorphic = camera.getClass().getMethod("setDisplayOrientation",
- new Class[]{int.class});
- if (downPolymorphic != null) {
- downPolymorphic.invoke(camera, new Object[]{i});
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- public static void setCameraDisplayOrientation(Activity activity,
- int cameraId, android.hardware.Camera camera) {
- android.hardware.Camera.CameraInfo info =
- new android.hardware.Camera.CameraInfo();
- android.hardware.Camera.getCameraInfo(cameraId, info);
- int rotation = activity.getWindowManager().getDefaultDisplay()
- .getRotation();
- int degrees = 0;
- switch (rotation) {
- case Surface.ROTATION_0:
- degrees = 0;
- break;
- case Surface.ROTATION_90:
- degrees = 90;
- break;
- case Surface.ROTATION_180:
- degrees = 180;
- break;
- case Surface.ROTATION_270:
- degrees = 270;
- break;
- }
- int result;
- if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
- result = (info.orientation + degrees) % 360;
- result = (360 - result) % 360; // compensate the mirror
- } else { // back-facing
- result = (info.orientation - degrees + 360) % 360;
- }
- camera.setDisplayOrientation(result);
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
- final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
- setMeasuredDimension(width, height);
- // if (mSupportedPreviewSizes != null) {
- // mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
- // }
- }
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- if (changed && getChildCount() > 0) {
- final View child = getChildAt(0);
- final int width = r - l;
- final int height = b - t;
- int previewWidth = width;
- int previewHeight = height;
- if (mPreviewSize != null) {
- previewWidth = mPreviewSize.width;
- previewHeight = mPreviewSize.height;
- }
- // Center the child SurfaceView within the parent.
- if (width * previewHeight > height * previewWidth) {
- final int scaledChildWidth = previewWidth * height / previewHeight;
- child.layout((width - scaledChildWidth) / 2, 0,
- (width + scaledChildWidth) / 2, height);
- } else {
- final int scaledChildHeight = previewHeight * width / previewWidth;
- child.layout(0, (height - scaledChildHeight) / 2,
- width, (height + scaledChildHeight) / 2);
- }
- }
- }
- public void surfaceCreated(SurfaceHolder holder) {
- // The Surface has been created, acquire the camera and tell it where
- // to draw.
- try {
- if (mCamera != null) {
- mCamera.setPreviewDisplay(holder);
- }
- } catch (IOException e) {
- if (null != mCamera) {
- mCamera.release();
- mCamera = null;
- }
- e.printStackTrace();
- }
- }
- public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
- if (holder.getSurface() == null) {
- return;
- }
- if (mCamera != null) {
- Camera.Parameters parameters = mCamera.getParameters();
- parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
- mCamera.setParameters(parameters);
- try {
- mCamera.setPreviewDisplay(holder);
- } catch (IOException e) {
- e.printStackTrace();
- }
- mCamera.startPreview();
- reAutoFocus();
- }
- }
- public void surfaceDestroyed(SurfaceHolder holder) {
- // Surface will be destroyed when we return, so stop the preview.
- if (mCamera != null) {
- mCamera.stopPreview();
- }
- }
- /**
- * 最小预览界面的分辨率
- */
- private static final int MIN_PREVIEW_PIXELS = 480 * 320;
- /**
- * 最大宽高比差
- */
- private static final double MAX_ASPECT_DISTORTION = 0.15;
- /**
- * 找出最适合的预览界面分辨率
- *
- * @return
- */
- private Camera.Size findBestPreviewResolution() {
- Camera.Parameters cameraParameters = mCamera.getParameters();
- Camera.Size defaultPreviewResolution = cameraParameters.getPreviewSize();
- List rawSupportedSizes = cameraParameters.getSupportedPreviewSizes();
- if (rawSupportedSizes == null) {
- return defaultPreviewResolution;
- }
- // 按照分辨率从大到小排序
- List supportedPreviewResolutions = new ArrayList(rawSupportedSizes);
- Collections.sort(supportedPreviewResolutions, new Comparator() {
- @Override
- public int compare(Camera.Size a, Camera.Size b) {
- int aPixels = a.height * a.width;
- int bPixels = b.height * b.width;
- if (bPixels aPixels) {
- return 1;
- }
- return 0;
- }
- });
- StringBuilder previewResolutionSb = new StringBuilder();
- for (Camera.Size supportedPreviewResolution : supportedPreviewResolutions) {
- previewResolutionSb.append(supportedPreviewResolution.width).append('x').append(supportedPreviewResolution.height)
- .append(' ');
- }
- // 移除不符合条件的分辨率
- double screenAspectRatio = (double) screenWidth
- / screenHeight;
- Iterator it = supportedPreviewResolutions.iterator();
- while (it.hasNext()) {
- Camera.Size supportedPreviewResolution = it.next();
- int width = supportedPreviewResolution.width;
- int height = supportedPreviewResolution.height;
- // 移除低于下限的分辨率,尽可能取高分辨率
- if (width * height height,我们设置的portrait模式中,width height;
- int maybeFlippedWidth = isCandidatePortrait ? height : width;
- int maybeFlippedHeight = isCandidatePortrait ? width : height;
- double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight;
- double distortion = Math.abs(aspectRatio - screenAspectRatio);
- if (distortion > MAX_ASPECT_DISTORTION) {
- it.remove();
- continue;
- }
- // 找到与屏幕分辨率完全匹配的预览界面分辨率直接返回
- if (maybeFlippedWidth == screenWidth
- && maybeFlippedHeight == screenHeight) {
- return supportedPreviewResolution;
- }
- }
- // 如果没有找到合适的,并且还有候选的像素,则设置其中最大比例的,对于配置比较低的机器不太合适
- if (!supportedPreviewResolutions.isEmpty()) {
- Camera.Size largestPreview = supportedPreviewResolutions.get(0);
- return largestPreview;
- }
- // 没有找到合适的,就返回默认的
- return defaultPreviewResolution;
- }
- private Camera.Size findBestPictureResolution() {
- Camera.Parameters cameraParameters = mCamera.getParameters();
- List supportedPicResolutions = cameraParameters.getSupportedPictureSizes(); // 至少会返回一个值
- StringBuilder picResolutionSb = new StringBuilder();
- for (Camera.Size supportedPicResolution : supportedPicResolutions) {
- picResolutionSb.append(supportedPicResolution.width).append('x')
- .append(supportedPicResolution.height).append(" ");
- }
- Camera.Size defaultPictureResolution = cameraParameters.getPictureSize();
- // 排序
- List sortedSupportedPicResolutions = new ArrayList(
- supportedPicResolutions);
- Collections.sort(sortedSupportedPicResolutions, new Comparator() {
- @Override
- public int compare(Camera.Size a, Camera.Size b) {
- int aPixels = a.height * a.width;
- int bPixels = b.height * b.width;
- if (bPixels aPixels) {
- return 1;
- }
- return 0;
- }
- });
- // 移除不符合条件的分辨率
- double screenAspectRatio = screenWidth
- / (double) screenHeight;
- Iterator it = sortedSupportedPicResolutions.iterator();
- while (it.hasNext()) {
- Camera.Size supportedPreviewResolution = it.next();
- int width = supportedPreviewResolution.width;
- int height = supportedPreviewResolution.height;
- // 在camera分辨率与屏幕分辨率宽高比不相等的情况下,找出差距最小的一组分辨率
- // 由于camera的分辨率是width>height,我们设置的portrait模式中,width height;
- int maybeFlippedWidth = isCandidatePortrait ? height : width;
- int maybeFlippedHeight = isCandidatePortrait ? width : height;
- double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight;
- double distortion = Math.abs(aspectRatio - screenAspectRatio);
- if (distortion > MAX_ASPECT_DISTORTION) {
- it.remove();
- continue;
- }
- }
- // 如果没有找到合适的,并且还有候选的像素,对于照片,则取其中最大比例的,而不是选择与屏幕分辨率相同的
- if (!sortedSupportedPicResolutions.isEmpty()) {
- return sortedSupportedPicResolutions.get(0);
- }
- // 没有找到合适的,就返回默认的
- return defaultPictureResolution;
- }
- private Size getOptimalPreviewSize(List sizes, int w, int h) {
- final double ASPECT_TOLERANCE = 0.1;
- double targetRatio = (double) w / h;
- if (sizes == null)
- return null;
- Size optimalSize = null;
- double minDiff = Double.MAX_VALUE;
- int targetHeight = h;
- // Try to find an size match aspect ratio and size
- for (Size size : sizes) {
- double ratio = (double) size.width / size.height;
- if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
- continue;
- if (Math.abs(size.height - targetHeight) 0) {
- List areas = new ArrayList();
- WindowManager wm = (WindowManager) getContext()
- .getSystemService(Context.WINDOW_SERVICE);
- //xy变换了
- int rectY = -x * 2000 / wm.getDefaultDisplay().getWidth() + 1000;
- int rectX = y * 2000 / wm.getDefaultDisplay().getHeight() - 1000;
- int left = rectX 900 ? 1000 : rectY + 100;
- Rect area1 = new Rect(left, top, right, bottom);
- areas.add(new Camera.Area(area1, 800));
- parameters.setMeteringAreas(areas);
- }
- parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
- }
- @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
- public void focusOnTouch(MotionEvent event) {
- Rect focusRect = calculateTapArea(event.getRawX(), event.getRawY(), 1f);
- Rect meteringRect = calculateTapArea(event.getRawX(), event.getRawY(), 1.5f);
- Camera.Parameters parameters = mCamera.getParameters();
- parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
- if (parameters.getMaxNumFocusAreas() > 0) {
- List focusAreas = new ArrayList();
- focusAreas.add(new Camera.Area(focusRect, 1000));
- parameters.setFocusAreas(focusAreas);
- }
- if (parameters.getMaxNumMeteringAreas() > 0) {
- List meteringAreas = new ArrayList();
- meteringAreas.add(new Camera.Area(meteringRect, 1000));
- parameters.setMeteringAreas(meteringAreas);
- }
- mCamera.setParameters(parameters);
- mCamera.autoFocus(this);
- }
- /**
- * Convert touch position x:y to {@link Camera.Area} position -1000:-1000 to 1000:1000.
- */
- private Rect calculateTapArea(float x, float y, float coefficient) {
- float focusAreaSize = 300;
- int areaSize = Float.valueOf(focusAreaSize * coefficient).intValue();
- int centerX = (int) (x / getResolution().width * 2000 - 1000);
- int centerY = (int) (y / getResolution().height * 2000 - 1000);
- int left = clamp(centerX - areaSize / 2, -1000, 1000);
- int right = clamp(left + areaSize, -1000, 1000);
- int top = clamp(centerY - areaSize / 2, -1000, 1000);
- int bottom = clamp(top + areaSize, -1000, 1000);
- return new Rect(left, top, right, bottom);
- }
- private int clamp(int x, int min, int max) {
- if (x > max) {
- return max;
- }
- if (x 0) {
- try {
- mCurrentCameraId = 0;
- camera = Camera.open(mCurrentCameraId);
- camera.startPreview();
- preview.setCamera(camera);
- preview.reAutoFocus();
- } catch (RuntimeException ex) {
- Toast.makeText(mContext, "未发现相机", Toast.LENGTH_LONG).show();
- }
- }
- }
- @Override
- protected void onPause() {
- if (camera != null) {
- camera.stopPreview();
- preview.setCamera(null);
- camera.release();
- camera = null;
- preview.setNull();
- }
- super.onPause();
- }
- private void resetCam() {
- camera.startPreview();
- preview.setCamera(camera);
- }
- ShutterCallback shutterCallback = new ShutterCallback() {
- public void onShutter() {
- }
- };
- PictureCallback rawCallback = new PictureCallback() {
- public void onPictureTaken(byte[] data, Camera camera) {
- }
- };
- PictureCallback jpegCallback = new PictureCallback() {
- public void onPictureTaken(byte[] data, Camera camera) {
- new SaveImageTask(data).execute();
- resetCam();
- }
- };
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- preview.pointFocus(event);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(
- focusIndex.getLayoutParams());
- layout.setMargins((int) event.getX() - 60, (int) event.getY() - 60, 0,0);
- focusIndex.setLayoutParams(layout);
- focusIndex.setVisibility(View.VISIBLE);
- ScaleAnimation sa = new ScaleAnimation(3f, 1f, 3f, 1f,
- ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
- ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
- sa.setDuration(800);
- focusIndex.startAnimation(sa);
- handler.postAtTime(new Runnable() {
- @Override
- public void run() {
- focusIndex.setVisibility(View.INVISIBLE);
- }
- }, 800);
- return false;
- }
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- /*case R.id.camera_back:
- setResult(0);
- finish();
- break;*/
- case R.id.camera_flip_view:
- switchCamera();
- break;
- case R.id.flash_view:
- turnLight(camera);
- break;
- case R.id.action_button:
- takePhoto();
- break;
- case R.id.search: //处理选中状态
- mBtnSearch.setSelected(true);
- mBtnTakePhoto.setSelected(false);
- break;
- case R.id.takephoto: //处理选中状态
- mBtnTakePhoto.setSelected(true);
- mBtnSearch.setSelected(false);
- break;
- }
- }
- private static String getCameraPath() {
- Calendar calendar = Calendar.getInstance();
- StringBuilder sb = new StringBuilder();
- sb.append("IMG");
- sb.append(calendar.get(Calendar.YEAR));
- int month = calendar.get(Calendar.MONTH) + 1; // 0~11
- sb.append(month
关注打赏
1482932726
查看更多评论
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?


微信扫码登录