Android自定义照相机详解

Android自定义照相机详解,第1张

概述几乎每个APP都会用的相机功能,下面小编把内容整理分享到编程小技巧平台,供大家参考,感兴趣的朋友一起学习吧!

几乎每个APP都会用的相机功能,下面小编把内容整理分享到编程小技巧平台,供大家参考,感兴趣的朋友一起学习吧!

启动相机的两种方式

1.直接启动系统相机

<code > Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); startActivity(intent);</code>

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

<code > Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromfile(mCurrentPhotofile));startActivityForResult(intent,CAMERA_WITH_DATA);</code>

2.自定义启动相机

今天以第二种为例。效果图如下

自定义相机的一般步骤

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

一般步骤的代码演示

<code >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,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);//设置类型}@OverrIDepublic voID surfaceCreated(SurfaceHolder holder) {Log.i(TAG,"surfaceCreated");if (mCamera == null) {mCamera = Camera.open();//开启相机try {mCamera.setPrevIEwdisplay(holder);//摄像头画面显示在Surface上} catch (IOException e) {e.printstacktrace();}}}@OverrIDepublic voID surfaceChanged(SurfaceHolder holder,int format,int wIDth,int height) {Log.i(TAG,"surfaceChanged");mCamera.startPrevIEw();}@OverrIDepublic voID surfaceDestroyed(SurfaceHolder holder) {Log.i(TAG,"surfaceDestroyed");mCamera.stopPrevIEw();//停止预览mCamera.release();//释放相机资源mCamera = null;holder = null;}@OverrIDepublic voID onautoFocus(boolean success,Camera Camera) {if (success) {Log.i(TAG,"onautoFocus success="+success);}}}</code>

添加相机和自动聚焦限权

<code ><uses-permission androID:name="androID.permission.CAMERA"><uses-feature androID:name="androID.harDWare.camera.autofocus"></uses-feature></uses-permission></code>

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

<code > private voID setCameraParams(Camera camera,"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></camera.size></camera.size></code>*/ private Camera.Size getProperSize(List 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

<code > // 拍照瞬间调用private Camera.ShutterCallback shutter = new Camera.ShutterCallback() {@OverrIDepublic voID onShutter() {Log.i(TAG,"shutter");}};// 获得没有压缩过的图片数据private Camera.PictureCallback raw = new Camera.PictureCallback() {@OverrIDepublic voID onPictureTaken(byte[] data,Camera Camera) {Log.i(TAG,"raw");}};//创建jpeg图片回调数据对象private Camera.PictureCallback jpeg = new Camera.PictureCallback() {@OverrIDepublic voID onPictureTaken(byte[] data,Camera Camera) {bufferedoutputstream bos = null;Bitmap bm = null;try {// 获得图片bm = BitmapFactory.decodeByteArray(data,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();}}}};</code>

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

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

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

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

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

真正的开始

昨天看见别的APP在照相的时候,屏幕上居然可以显示像效果图那样的框框啦、辅助点啦、图片bulabulabula~。在网上搜索一番实现方式,再加上一些自己的理解,构成了这篇博客。

上文布局文件一直没有贴,现在贴出来大家先扫一眼,有些控件会在接下来展示

<code ><!--?xml version="1.0" enCoding="utf-8"?--><framelayout androID:layout_height="match_parent" androID:layout_wIDth="match_parent" xmlns:androID="http://schemas.androID.com/apk/res/androID"><com.dyk.cameratest.vIEw.camerasurfacevIEw androID:ID="@+ID/cameraSurfaceVIEw" androID:layout_height="match_parent" androID:layout_wIDth="match_parent"><com.dyk.cameratest.vIEw.rectoncamera androID:layout_height="match_parent" androID:layout_wIDth="match_parent"><relativelayout androID:layout_height="match_parent" androID:layout_wIDth="match_parent"></relativelayout></com.dyk.cameratest.vIEw.rectoncamera></com.dyk.cameratest.vIEw.camerasurfacevIEw></framelayout></code><button androID:background="#88427ac7" androID:ID="@+ID/takePic" androID:layout_alignparentbottom="true" androID:layout_centerhorizontal="true" androID:layout_height="50dp" androID:layout_marginbottom="20dp" androID:layout_wIDth="80dp" androID:text="拍照" androID:textcolor="#aaa"><code ></code></button>

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

自定义边边框框

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

RectOnCamera

<code >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,0);}public RectOnCamera(Context context,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);}@OverrIDeprotected 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,radio - 20,mPaint); // 内圆}}</code>

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

动起来

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

<code > @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;}</code>

其次,定义回调接口

<code > private IautoFocus mIautoFocus;/** 聚焦的回调接口 */public interface IautoFocus{voID autoFocus();}public voID setIautoFocus(IautoFocus mIautoFocus) {this.mIautoFocus = mIautoFocus;}</code>

在ontouchEvent()中return前加入

<code > if (mIautoFocus != null){mIautoFocus.autoFocus();}</code>

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

<code > public voID setautoFocus(){mCamera.autoFocus(this);}</code>

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

<code >public class MainActivity extends Activity implements VIEw.OnClickListener,RectOnCamera.IautoFocus{private CameraSurfaceVIEw mCameraSurfaceVIEw;private RectOnCamera mRectOnCamera;private button takePicBtn;private boolean isClicked;@OverrIDeprotected 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);}@OverrIDepublic voID onClick(VIEw v) {switch (v.getID()){case R.ID.takePic:mCameraSurfaceVIEw.takePicture();break;default:break;}}@OverrIDepublic voID autoFocus() {mCameraSurfaceVIEw.setautoFocus();}}</code>

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

好了,AndroID自定义照相机教程到此结束,希望对大家有所帮助!

编程小技巧推荐阅读:

Android开发从相机或相册获取图片裁剪

Android自定义照相机倒计时拍照

总结

以上是内存溢出为你收集整理的Android自定义照相机详解全部内容,希望文章能够帮你解决Android自定义照相机详解所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1149465.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存