概述

相机几乎是每个APP都要用到的功能,万一老板让你定制相机方不方?反正我是有点方。关于相机的两天奋斗总结免费送给你。

启动相机的两种方式

1.直接启动系统相机

  Intent intent = new Intent();  intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);  startActivity(intent);

或者指定返回图片的名称mCurrentPhotoFile

        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(mCurrentPhotoFile)); startActivityForResult(intent, CAMERA_WITH_DATA);

2.自定义启动相机 
今天以第二种为例。效果图如下

自定义相机的一般步骤

  1. 创建显示相机画面的布局,Android已经为我们选定好SurfaceView
  2. 通过SurfaceView#getHolder()获得链接Camera和SurfaceView的SurfaceHolder
  3. Camame.open()打开相机
  4. 通过SurfaceHolder链接Camera和SurfaceView

一般步骤的代码演示

public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Camera.AutoFocusCallback { private static final String TAG = "CameraSurfaceView"; private Context mContext; private SurfaceHolder holder; private Camera mCamera; private int mScreenWidth; private int mScreenHeight; public CameraSurfaceView(Context context) { this(context, null); } public CameraSurfaceView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CameraSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; getScreenMetrix(context); initView(); } private void getScreenMetrix(Context context) { WindowManager WM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); WM.getDefaultDisplay().getMetrics(outMetrics); mScreenWidth = outMetrics.widthPixels; mScreenHeight = outMetrics.heightPixels; } private void initView() { holder = getHolder();//获得surfaceHolder引用 holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//设置类型 } @Override public void surfaceCreated(SurfaceHolder holder) { Log.i(TAG, "surfaceCreated"); if (mCamera == null) { mCamera = Camera.open();//开启相机 try { mCamera.setPreviewDisplay(holder);//摄像头画面显示在Surface上 } catch (IOException e) { e.printStackTrace(); } } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.i(TAG, "surfaceChanged"); mCamera.startPreview(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.i(TAG, "surfaceDestroyed"); mCamera.stopPreview();//停止预览 mCamera.release();//释放相机资源 mCamera = null; holder = null; } @Override public void onAutoFocus(boolean success, Camera Camera) { if (success) { Log.i(TAG, "onAutoFocus success="+success); } } }

添加相机和自动聚焦限权

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.autofocus" />

将CameraSurfaceView放在布局文件中,这里建议最外层为FrameLayout,后面会用到。如此,我们便有了一个没有照相功能的相机。初次之外,仔细观察相机显示画面,图片是不是变形严重?那是因为我们还没有为相机设置各种参数。在预览前要设置摄像头的分辨率、预览分辨率和图片分辨率的宽高比保持一致。这样图片才不会变形。这是个比较难以理解的部分,想深刻理解还需读者自己动手去实践。

   private void setCameraParams(Camera camera, int width, int height) { Log.i(TAG,"setCameraParams width="+width+" height="+height); Camera.Parameters parameters = mCamera.getParameters(); // 获取摄像头支持的PictureSize列表 List<Camera.Size> pictureSizeList = parameters.getSupportedPictureSizes(); for (Camera.Size size : pictureSizeList) { Log.i(TAG, "pictureSizeList size.width=" + size.width + " size.height=" + size.height); } /**从列表中选取合适的分辨率*/ Camera.Size picSize = getProperSize(pictureSizeList, ((float) height / width)); if (null == picSize) { Log.i(TAG, "null == picSize"); picSize = parameters.getPictureSize(); } Log.i(TAG, "picSize.width=" + picSize.width + " picSize.height=" + picSize.height); // 根据选出的PictureSize重新设置SurfaceView大小 float w = picSize.width; float h = picSize.height; parameters.setPictureSize(picSize.width,picSize.height); this.setLayoutParams(new FrameLayout.LayoutParams((int) (height*(h/w)), height)); // 获取摄像头支持的PreviewSize列表 List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes(); for (Camera.Size size : previewSizeList) { Log.i(TAG, "previewSizeList size.width=" + size.width + " size.height=" + size.height); } Camera.Size preSize = getProperSize(previewSizeList, ((float) height) / width); if (null != preSize) { Log.i(TAG, "preSize.width=" + preSize.width + " preSize.height=" + preSize.height); parameters.setPreviewSize(preSize.width, preSize.height); } parameters.setJpegQuality(100); // 设置照片质量 if (parameters.getSupportedFocusModes().contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 连续对焦模式 } mCamera.cancelAutoFocus();//自动对焦。 mCamera.setDisplayOrientation(90);// 设置PreviewDisplay的方向,效果就是将捕获的画面旋转多少度显示 mCamera.setParameters(parameters); } /** * 从列表中选取合适的分辨率 * 默认w:h = 4:3 * <p>注意:这里的w对应屏幕的height * h对应屏幕的width<p/> */ private Camera.Size getProperSize(List<Camera.Size> pictureSizeList, float screenRatio) { Log.i(TAG, "screenRatio=" + screenRatio); Camera.Size result = null; for (Camera.Size size : pictureSizeList) { float currentRatio = ((float) size.width) / size.height; if (currentRatio - screenRatio == 0) { result = size; break; } } if (null == result) { for (Camera.Size size : pictureSizeList) { float curRatio = ((float) size.width) / size.height; if (curRatio == 4f / 3) {// 默认w:h = 4:3 result = size; break; } } } return result; }

进去的是屏幕宽高,出来的是调整好了的参数。在surfaceChanged方法中执行mCamera.startPreview(); 前调用setCameraParams(mCamera, mScreenWidth, mScreenHeight); 就可以了。最后要在AndroidManifest.xml里设置activity的方向android:screenOrientation="portrait"代码里有很多注释,其中也有我自己调试时候的Log,大家可以自己调试下,看看不同参数的效果。昨天调参数搞到一点多,都在折腾这个函数。唉,一把辛酸泪。 
身为一个相机,居然不能照相?真是太丢脸了!下面给我们的相机添加上照相的功能。照相核心代码就一句:mCamera.takePicture(null, null, jpeg); 
可以看到takePicture方法有三个参数,分别是ShutterCallback、PictureCallback和PictureCallback。这里我们只用了PictureCallback

    // 拍照瞬间调用private Camera.ShutterCallback shutter = new Camera.ShutterCallback() {@Overridepublic void onShutter() { Log.i(TAG,"shutter"); } }; // 获得没有压缩过的图片数据 private Camera.PictureCallback raw = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera Camera) { Log.i(TAG, "raw"); } }; //创建jpeg图片回调数据对象 private Camera.PictureCallback jpeg = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera Camera) { BufferedOutputStream bos = null; Bitmap bm = null; try { // 获得图片 bm = BitmapFactory.decodeByteArray(data, 0, data.length); if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { Log.i(TAG, "Environment.getExternalStorageDirectory()="+Environment.getExternalStorageDirectory()); String filePath = "/sdcard/dyk"+System.currentTimeMillis()+".jpg";//照片保存路径 File file = new File(filePath); if (!file.exists()){ file.createNewFile(); } bos = new BufferedOutputStream(new FileOutputStream(file)); bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);//将图片压缩到流中 }else{ Toast.makeText(mContext,"没有检测到内存卡", Toast.LENGTH_SHORT).show(); } } catch (Exception e) { e.printStackTrace(); } finally { try { bos.flush();//输出 bos.close();//关闭 bm.recycle();// 回收bitmap空间 mCamera.stopPreview();// 关闭预览 mCamera.startPreview();// 开启预览 } catch (IOException e) { e.printStackTrace(); } } } };

在jpeg的onPictureTaken里。我们将存储照片信息的byte[] data解析成bitmap,然后转换成JPG格式的图片保存在SD卡中。注意finally中最后两句mCamera.stopPreview();// 关闭预览 mCamera.startPreview();// 开启预览 上文也提到:当调用camera.takePiture方法后,camera关闭了预览,这时需要调用startPreview()来重新开启预览。如果不再次开启预览,则会一直停留在拍摄照片画面。为了方便外部调用拍照。这里我暴露了一个方法供外部拍照。

    public void takePicture(){//设置参数,并拍照setCameraParams(mCamera, mScreenWidth, mScreenHeight);// 当调用camera.takePiture方法后,camera关闭了预览,这时需要调用startPreview()来重新开启预览 mCamera.takePicture(null, null, jpeg); }

在布局文件中添加一个Button,点击Button执行takePicture()方法。不要忘了添加写SD卡限权

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

至此,一个具有照相并保存拍摄图片功能的相机就做出来了。But,我们就此满足了吗?要是为了这些简单的功能我也不会写这篇博客。这只是个开始

真正的开始

昨天看见别的APP在照相的时候,屏幕上居然可以显示像效果图那样的框框啦、辅助点啦、图片bulabulabula~。在网上搜索一番实现方式,再加上一些自己的理解,构成了这篇博客。 
上文布局文件一直没有贴,现在贴出来大家先扫一眼,有些控件会在接下来展示

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.dyk.cameratest.view.CameraSurfaceView android:id="@+id/cameraSurfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> <com.dyk.cameratest.view.RectOnCamera android:layout_width="match_parent" android:layout_height="match_parent" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="20dp" android:id="@+id/takePic" android:layout_width="80dp" android:layout_height="50dp" android:background="#88427ac7" android:text="拍照" android:textColor="#aaa" /> </RelativeLayout> </FrameLayout> 

布局文件的最外层是个FrameLayout,我们知道FrameLayout是自带覆盖效果的。由来这个思路接下来就很简单了。编程重要的是思想,思想有了,其余的就剩具体的实现细节。

自定义边边框框

为了和CameraSurfaceView区分开,再自定义一个RectOnCamera专门用来画边边框框这些东西。这样做还一个好处是方便维护,不至于将所有东西都放在一个View中。

RectOnCamera

package com.dyk.cameratest.view;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Point; import android.graphics.RectF; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; /** * Created by dyk on 2016/4/7. */ public class RectOnCamera extends View { private static final String TAG = "CameraSurfaceView"; private int mScreenWidth; private int mScreenHeight; private Paint mPaint; private RectF mRectF; // 圆 private Point centerPoint; private int radio; public RectOnCamera(Context context) { this(context, null); } public RectOnCamera(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RectOnCamera(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); getScreenMetrix(context); initView(context); } private void getScreenMetrix(Context context) { WindowManager WM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); WM.getDefaultDisplay().getMetrics(outMetrics); mScreenWidth = outMetrics.widthPixels; mScreenHeight = outMetrics.heightPixels; } private void initView(Context context) { mPaint = new Paint(); mPaint.setAntiAlias(true);// 抗锯齿 mPaint.setDither(true);// 防抖动 mPaint.setColor(Color.RED); mPaint.setStrokeWidth(5); mPaint.setStyle(Paint.Style.STROKE);// 空心 int marginLeft = (int) (mScreenWidth*0.15); int marginTop = (int) (mScreenHeight * 0.25); mRectF = new RectF(marginLeft, marginTop, mScreenWidth - marginLeft, mScreenHeight - marginTop); centerPoint = new Point(mScreenWidth/2, mScreenHeight/2); radio = (int) (mScreenWidth*0.1); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setColor(Color.RED); canvas.drawRect(mRectF, mPaint); mPaint.setColor(Color.WHITE); Log.i(TAG, "onDraw"); canvas.drawCircle(centerPoint.x,centerPoint.y, radio,mPaint);// 外圆 canvas.drawCircle(centerPoint.x,centerPoint.y, radio - 20,mPaint); // 内圆 } } 

这里简单的画了一个类似二维码扫描的框框,还有一个类似聚焦的内外圆。那么问题来了,聚焦的内外圆要随着手指滑而改变位置,而且要有聚焦的效果。可又和具有聚焦功能的CameraSurfaceView不是同一个类,不仅如此聚焦内外圆还完全覆盖了CameraSurfaceView。要处理这种问题,需要接口回调。这就是思想下面的细节。现在虽然确定接口回调,但还有一个问题,CameraSurfaceView类和RectOnCamera类中都没有对方的对象或者引用。没错,通过共同持有RectOnCamera和CameraSurfaceView的Activity可以实现此功能。下面是具体的实现方法。

动起来

首先,想要随着手指的滑动而改变RectOnCamera的位置肯定是要复写onTouchEvent()方法

   @Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()){ case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_UP: int x = (int) event.getX(); int y = (int) event.getY(); centerPoint = new Point(x, y); invalidate(); return true; } return true; }

其次,定义回调接口

 private IAutoFocus mIAutoFocus;/** 聚焦的回调接口 */public interface IAutoFocus{ void autoFocus(); } public void setIAutoFocus(IAutoFocus mIAutoFocus) { this.mIAutoFocus = mIAutoFocus; }

在onTouchEvent()中return前加入

  if (mIAutoFocus != null){mIAutoFocus.autoFocus();}

至此我们的回调接口已经定义好了,此时还需要CameraSurfaceView暴露一个聚焦方法,以便Activity调用

    public void setAutoFocus(){mCamera.autoFocus(this);}

准备工作已经全部完成,下面请看Activity的具体实现:

public class MainActivity extends Activity implements View.OnClickListener,RectOnCamera.IAutoFocus{ private CameraSurfaceView mCameraSurfaceView; private RectOnCamera mRectOnCamera; private Button takePicBtn; private boolean isClicked; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); // 全屏显示 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_main); mCameraSurfaceView = (CameraSurfaceView) findViewById(R.id.cameraSurfaceView); mRectOnCamera = (RectOnCamera) findViewById(R.id.rectOnCamera); takePicBtn= (Button) findViewById(R.id.takePic); mRectOnCamera.setIAutoFocus(this); takePicBtn.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.takePic: mCameraSurfaceView.takePicture(); break; default: break; } } @Override public void autoFocus() { mCameraSurfaceView.setAutoFocus(); } } 

可以看到,MainActivity实现了IAutoFocus接口,并且在复写的IAutoFocus#autoFocus()方法中,调用了CameraSurfaceView暴露出来的方法setAutoFocus()。至此,在RectOnCamera每次的滑动过程中都会改变聚焦内外圆的位置,还会增加聚焦功能。一心二用甚至一心多用岂不是更好。

结束语

在经历两次没保存断电和一次CSDN服务器错误内容丢失之后,终究还是完成了这篇博客,实属不易。感谢能听我啰嗦到结尾~

PS:此Demo界面并没有做的很精致,只是提供了一种思路。按照此思路能做出比较华丽的效果,授人以鱼不如授人以渔。


源码下载:http://download.csdn.net/detail/qq_17250009/9484160

Android 自定义相机相关推荐

  1. android 实现自动拍照,Android自定义相机实现定时拍照功能

    这篇博客为大家介绍Android自定义相机,并且实现倒计时拍照功能. 首先自定义拍照会用到SurfaceView控件显示照片的预览区域,以下是布局文件: activity_main.xml andro ...

  2. android 自定义相机源码,Android 自定义相机及分析源码

    Android 自定义相机及分析源码 使用Android 系统相机的方法: 要想让应用有相机的action,咱们就必须在清单文件中做一些声明,好让系统知道,如下 action的作用就是声明action ...

  3. android自动对焦第一次对焦,Android自定义相机实现自动对焦和手动对焦

    Android自定义相机实现自动对焦和手动对焦: 不调用系统相机,因为不同的机器打开相机呈现的界面不统一也不能满足需求. 所以为了让程序在不同的机器上呈现出统一的界面,并且可以根据需求进行布局,做了此 ...

  4. Android自定义相机实现定时拍照

    这篇博客为大家介绍Android自定义相机,并且实现倒计时拍照功能. 首先自定义拍照会用到SurfaceView控件显示照片的预览区域,以下是布局文件: activity_main.xml <F ...

  5. Android 自定义相机Demo 入门学习

    Android 自定义相机Demo 本文是参考网上一些自定义相机示例,再结合自己对相机的功能需求写的,基本上包含了很多基本功能,比如相机对焦.闪光灯,以及在手机预览界面上绘制自己想要绘制的图案. 话不 ...

  6. Android自定义相机拍照、图片裁剪的实现

    原文:Android自定义相机拍照.图片裁剪的实现 最近项目里面又要加一个拍照搜题的功能,也就是用户对着不会做的题目拍一张照片,将照片的文字使用ocr识别出来,再调用题库搜索接口搜索出来展示给用户,类 ...

  7. Android自定义相机,切换前后摄像头,照相机拍照

    Android自定义相机,简单实现切换前后摄像头,照相机拍照 Ctrl +C  Ctrl+V 可以直接 run 起来,注释比较详细;源码下载 <?xml version="1.0&qu ...

  8. android 自定义相机,Android自定义相机实现定时拍照功能

    这篇博客为大家介绍Android自定义相机,并且实现倒计时拍照功能. 首先自定义拍照会用到SurfaceView控件显示照片的预览区域,以下是布局文件: activity_main.xml andro ...

  9. Android自定义相机详细讲解

    使用surfaceview自定义相机,同时把自己踩过的坑分享给大家,希望大家有所收获. #写在前面的话 前一阵子负责一个自定义相机进行拍照并在另一个页面进行人脸识别的模块,相机部分需求并不怎么复杂,可 ...

  10. android 自定义相机,Camera,相机遮罩层

    实现效果:      实现方法:一个自定义view实现demo 一.自定义相机Camera,无API版本限制 (1).实现接口 implements SurfaceHolder.Callback pr ...

最新文章

  1. linux 文件拷贝io,NIO拷贝文件真的比IO拷贝文件效率高?
  2. 【AI】Win10-Tensorflow
  3. 宽度高度sizeWithFont:constrainedToSize:lineBreakMode的使用
  4. linux操作系统分析实验—基于mykernel的时间片轮转多道程序实现与分析
  5. 也来玩玩MongoDB
  6. lable里的字体颜色_?APP界面的字体规范!
  7. 聊一聊我在腾讯的外包同事
  8. Ubuntu 各版本号和名称对照
  9. 学嵌入式为什么要学Linux?
  10. APL平台测试版推出.
  11. 常用软件密码破解完全指南[转]
  12. FLASH中button组件的selected和toggle属性解析
  13. uni-app 页面跳转
  14. Pytorch → ONNX → TensorRT
  15. MapReduce自定义排序、分区、分组案例
  16. 程序员该该怎么样转型 5G 开发呢?
  17. stata移动平均插值法mipolate命令
  18. 浏览器自动注入js脚本
  19. Python中10个常用的内置函数
  20. 新手的linux之旅 五、安装IE浏览器

热门文章

  1. [转] C#2010 在TreeView控件下显示路径下所有文件和文件夹
  2. Win7电脑开启局域网连接和共享过程中出现的您可能没有权限使用网络资源的解决办法...
  3. Flash Player版本相关问题
  4. 用FileMapping跨进程共享数据
  5. BXP_4.1安装配置及域应用随记一
  6. TCP/IP卷一实验之------EIGRP
  7. hibernate的环境搭建
  8. 《机器学习与数据科学(基于R的统计学习方法)》——2.8 读取JSON文件
  9. Android四大组件之——Activity(一)定义、状态和后退栈(图文详解)
  10. 一些看起来有用但没用过的函数