相机开发(聚焦、横竖屏拍照、照片存储、连续拍照等)
近期项目用到了相机拍照的功能,于是想着封装好一些通用性较好的相机调用,从百度和谷歌上查找出来的资料真的印证了“天下文章一大抄”的道理,并且它们实现的拍照功能大都存在缺陷,如聚焦问题、反复拍照问题、照片存储问题、横竖屏转换问题。一大堆的问题,并且程序的扩展性和可重用性实在不敢恭维,排版级其混乱。
最后无奈,打开API文档camera相机类,从最基础的学起,然后自己进行改进,从这里也告诉我们一个道理,API文档才是学习起点,由于它会告诉你整个实现的原理和原因,可以对整个框架有一个总体的了解,看完API文档看其它的就有事半功倍的效果,吐槽完成,以下来正式实现。
一.实现流程
这幅图是从API文档(最好是看英文版的)整理出来的,从这副图上面我们能够看出,主要是有6步,当中难点是创建相机预览类。
二.权限声明
这个不讲了,直接增加声明权限代码,不明确的能够网上查查看
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
三 检查相机和获取相机实例
新建CameraCheck类,主要有2个方法,代码例如以下
public class CameraCheck { public static boolean CheckCamera(Context mContext) { if (mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_CAMERA)) { return true; } else { Toast.makeText(mContext, "相机不存在!", Toast.LENGTH_SHORT).show(); return false; } } /** A safe way to get an instance of the Camera object. */ public static Camera getCameraInstance(Context mContext) { Camera c = null; if (CheckCamera(mContext)) { try { c = Camera.open(); } catch (Exception e) { c=null; } } return c; // returns null if camera is unavailable } }
第一个方法用来检查相机是否存在,这种方法是来自API文档,用法
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)
hasSystemFeature(String name)方法返回设备是否支持name功能的真假值;
通过方法getCameraInstance返回相机的实例,通过调用该方法,mContext可以获得该相机资源,仅仅有获得了该相机资源才可以对相机进行操作。
四.创建相机预览类(重点)
我们在拍照之前须要对取景进行预览,这里我们须要使用SurfaceView控件,关于SurfaceView控件我们先简单的了解一下(别急,磨刀不误砍柴工)。
SurfaceView是View的子类,所以它拥有View的一切方法和属性,这一点我们从命名上面就能够看出来,比方绘制方法、大小等属性;它比View多了一个Surface的东西,Surface是专门用来绘制的类,而SurfaceView能够控制surface绘制的大小、位置等等;
可能有人会问,那为什么要专门这样一个类来绘制呢?不是有OnDraw()方法吗?相比于OnDraw()方法它有非常多优势,例如以下总结:
(1)在频繁更新UI线程的情况下,能够使用封装好的surface来频繁的更新,由于surface能够使用后台线程对UI界面进行绘制,而OnDraw()等绘制方法非常难做到(除非你频繁的调用handler来更新主界面,这得多麻烦啊!);
(2)SurfaceView能够用来绘制2D或者3D图形,绘制一些动态曲线等,它显示的速度会比一般的快非常多,由于他是通过硬件加速的方式来绘制的。
(3)它能够用来接受硬件的数据来绘制图像。
所以,通过以上几点我们能够知道,用它来接受相机的预览是理所当然的。那么它的用法是怎么样的呢?下创建一个surfaceView的继承类一般须要实现例如以下几个方法:
(1)surfaceCreated(SurfaceHolderholder):在该类创建的时候调用,这里一般须要实现一些初始化的工作,SurfaceHodler用来设定surface的大小位置等等;
(2)surfaceChanged(SurfaceHolderholder, int format, int width,int height)在surface大小发生改变时候调用,这里实现图形的绘制;
(3)surfaceDestroyed(SurfaceHolderholder)在surface销毁时候调用,这里一般对资源进行释放;
(4)实现SurfaceHodler.CallBack回调方法,在surfaceView创建完毕后自己主动调用类本身;
在实现之前我们先来看我们的需求,我们要实现的功能:预览、拍照、自己主动聚焦、触摸聚焦、连续拍照、照片存储。以下我们来创建一个SurfaceView类CameraPreview,它继承了SurfaceView,并实现接口SurfaceHolder.Callback
因此我们须要在surfaceCreated方法中创建一个camer实例,这个实例能够在这个类中进行调用,实现代码例如以下:
/** * 创建的时候自己主动调用该方法 */ @Override public void surfaceCreated(SurfaceHolder holder) { if (mCamera == null) { mCamera = CameraCheck.getCameraInstance(mContext); } try { if(mCamera!=null){ mCamera.setPreviewDisplay(holder); } } catch (IOException e) { if (null != mCamera) { mCamera.release(); mCamera = null; isPreview=false; } e.printStackTrace(); } }
这句代码 mCamera.setPreviewDisplay(holder)的意思是创建一个预览的hodler;我们在surfaceChanged中进行预览窗体的绘制调用的是startPreview()方法来開始绘制,代码例如以下:
/** * 当surface的大小发生改变的时候自己主动调用的 */ @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (mHolder.getSurface() == null) { return; } try { setCameraParms(); mCamera.setPreviewDisplay(holder); mCamera.startPreview(); reAutoFocus(); } catch (Exception e) { Log.d(TAG, "Error starting camera preview: " + e.getMessage()); } }
当中有2个比較关键的方法没有实现, setCameraParms()和reAutoFocus(),setCameraParms();函数用来设置预览图片的參数,当中关键的为预览图片的大小和拍照保存的尺寸大小,非常多的网上实现的程序拍出来的照片非常小模糊的原因就是没有设置好照片的尺寸,这个照片的尺寸是依据手机本身可以支持的尺寸有非常大关系。reAutoFocus()是自己主动聚焦的方法,须要动态获取reAutoFocus()函数是自己主动聚焦的实现;
我们首先来看一下setCameraParms()方法的实现:
private void setCameraParms(){ Camera.Parameters myParam = mCamera.getParameters(); List<Camera.Size> mSupportedsizeList =myParam.getSupportedPictureSizes(); if(mSupportedsizeList.size() > 1) { Iterator<Camera.Size> itos = mSupportedsizeList.iterator(); while (itos.hasNext()){ Camera.Size curSize = itos.next(); int curSupporSize=curSize.width * curSize.height; int fixPictrueSize= setFixPictureWidth * setFixPictureHeight; if( curSupporSize>fixPictrueSize && curSupporSize <= maxPictureSize) { setFixPictureWidth = curSize.width; setFixPictureHeight = curSize.height; } } }<pre name="code" class="java"> if (setFixPictureWidth != 0 && setFixPictureHeight != 0) { myParam.setPictureSize(setFixPictureWidth , setFixPictureHeight); }
myParam.setJpegQuality(100);mCamera.setParameters(myParam);if (myParam.getMaxNumDetectedFaces() > 0){ mCamera.startFaceDetection();}}
通过myParam.getSupportedPictureSizes();获取到手机支持的全部尺寸的枚举,并设置最大的固定尺寸这里设置最大为maxPictureSize = 5000000
reAutoFocus()的实现为:
<span style="white-space:pre"> </span>/** * Call the camera to Auto Focus */ public void reAutoFocus() { if (isSupportAutoFocus) { mCamera.autoFocus(new AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { } }); } }
使用回调函数autoFocus来实现自己主动聚焦
五.拍照
拍照方法有一个难点是横竖屏拍照的转换和存储,网上大都实现的是默认的横屏拍照,一旦换成竖屏后预览就会出现故障,并且存储的照片也有问题,因此为了解决问题,我们须要时刻监听方向传感器的变化,得到当前的旋转角度,我么能够通过调用OrientationEventListener系统监听类来得到当前角度,自己定义MyOrientationDetector代码例如以下:
/** * 方向变化监听器,监听传感器方向的改变 * @author zw.yan * */ public class MyOrientationDetector extends OrientationEventListener{ int Orientation; public MyOrientationDetector(Context context ) { super(context ); } @Override public void onOrientationChanged(int orientation) { Log.i("MyOrientationDetector ","onOrientationChanged:"+orientation); this.Orientation=orientation; Log.d("MyOrientationDetector","当前的传感器方向为"+orientation); } public int getOrientation(){ return Orientation; } }
在预览类中我们定义拍照方法TakePhone(),代码例如以下:
/** * 调整照相的方向,设置拍照相片的方向 */ private void takePhoto() { cameraOrientation = new MyOrientationDetector(mContext); if (mCamera != null) { int orientation = cameraOrientation.getOrientation(); Camera.Parameters cameraParameter = mCamera.getParameters(); cameraParameter.setRotation(90); cameraParameter.set("rotation", 90); if ((orientation >= 45) && (orientation < 135)) { cameraParameter.setRotation(180); cameraParameter.set("rotation", 180); } if ((orientation >= 135) && (orientation < 225)) { cameraParameter.setRotation(270); cameraParameter.set("rotation", 270); } if ((orientation >= 225) && (orientation < 315)) { cameraParameter.setRotation(0); cameraParameter.set("rotation", 0); } mCamera.setParameters(cameraParameter); mCamera.takePicture(shutterCallback, pictureCallback, mPicture); } }
在角度范围内自己主动调整旋转图片的角度,详细旋转的方式如代码,从而使存储的图片可以正常显示。
六.图片保存
在拍照时须要对图片进行保存,可是不能影响图片的下一次拍照,因此我们须要採用异步线程的方式,能够使用AsyncTask类,在拍照完毕时进行调用例如以下代码:
public class SavePictureTask extends AsyncTask<byte[], String, String> { @SuppressLint("SimpleDateFormat") @Override protected String doInBackground(byte[]... params) { File pictureFile = FileUtil.getOutputMediaFile(MEDIA_TYPE_IMAGE, mContext); if (pictureFile == null) { Toast.makeText(mContext, "请插入存储卡!", Toast.LENGTH_SHORT).show(); return null; } try { FileOutputStream fos = new FileOutputStream(pictureFile); fos.write(params[0]); fos.flush(); fos.close(); } catch (FileNotFoundException e) { Log.d(TAG, "File not found: " + e.getMessage()); } catch (IOException e) { Log.d(TAG, "Error accessing file: " + e.getMessage()); } return null; } }
这是基本对文件异步线程的IO操作有什么不明确的能够去看相应的API文档。
以下我将整个类贴出来:
/** * sufaceView 的预览类,当中SurfaceHolder.CallBack用来监听Surface的变化, * 当Surface发生改变的时候自己主动调用该回调方法 * 通过调用方SurfaceHolder.addCallBack来绑定该方法 * @author zw.yan * */ public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private String TAG = "CameraPreview"; /** * Surface的控制器,用来控制预览等操作 */ private SurfaceHolder mHolder; /** * 相机实例 */ private Camera mCamera = null; /** * 图片处理 */ public static final int MEDIA_TYPE_IMAGE = 1; /** * 预览状态标志 */ private boolean isPreview = false; /** * 设置一个固定的最大尺寸 */ private int maxPictureSize = 5000000; /** * 是否支持自己主动聚焦,默认不支持 */ private Boolean isSupportAutoFocus = false; /** * 获取当前的context */ private Context mContext; /** * 当前传感器的方向,当方向发生改变的时候可以自己主动从传感器管理类接受通知的辅助类 */ MyOrientationDetector cameraOrientation; /** * 设置最适合当前手机的图片宽度 */ int setFixPictureWidth = 0; /** * 设置当前最适合的图片高度 */ int setFixPictureHeight = 0; @SuppressWarnings("deprecation") public CameraPreview(Context context) { super(context); this.mContext = context; isSupportAutoFocus = context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_CAMERA_AUTOFOCUS); mHolder = getHolder(); //兼容android 3.0下面的API,假设超过3.0则不须要设置该方法 if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB){ mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } mHolder.addCallback(this);//绑定当前的回调方法 } /** * 创建的时候自己主动调用该方法 */ @Override public void surfaceCreated(SurfaceHolder holder) { if (mCamera == null) { mCamera = CameraCheck.getCameraInstance(mContext); } try { if(mCamera!=null){ mCamera.setPreviewDisplay(holder); } } catch (IOException e) { if (null != mCamera) { mCamera.release(); mCamera = null; isPreview=false; } e.printStackTrace(); } } /** * 当surface的大小发生改变的时候自己主动调用的 */ @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (mHolder.getSurface() == null) { return; } try { setCameraParms(); mCamera.setPreviewDisplay(holder); mCamera.startPreview(); reAutoFocus(); } catch (Exception e) { Log.d(TAG, "Error starting camera preview: " + e.getMessage()); } } private void setCameraParms(){ Camera.Parameters myParam = mCamera.getParameters(); List<Camera.Size> mSupportedsizeList =myParam.getSupportedPictureSizes(); if(mSupportedsizeList.size() > 1) { Iterator<Camera.Size> itos = mSupportedsizeList.iterator(); while (itos.hasNext()){ Camera.Size curSize = itos.next(); int curSupporSize=curSize.width * curSize.height; int fixPictrueSize= setFixPictureWidth * setFixPictureHeight; if( curSupporSize>fixPictrueSize && curSupporSize <= maxPictureSize) { setFixPictureWidth = curSize.width; setFixPictureHeight = curSize.height; } } }<pre name="code" class="java"> if (setFixPictureWidth != 0 && setFixPictureHeight != 0) { myParam.setPictureSize(setFixPictureWidth , setFixPictureHeight); }<pre name="code" class="java"><span style="white-space:pre"> </span>myParam.setJpegQuality(100); mCamera.setParameters(myParam); if (myParam.getMaxNumDetectedFaces() > 0){ mCamera.startFaceDetection(); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { mCamera.stopPreview(); mCamera.release(); mCamera = null; } /** * Call the camera to Auto Focus */ public void reAutoFocus() { if (isSupportAutoFocus) { mCamera.autoFocus(new AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { } }); } } /** * 自己主动聚焦,然后拍照 */ public void takePicture() { if (mCamera != null) { mCamera.autoFocus(autoFocusCallback); } } private AutoFocusCallback autoFocusCallback = new AutoFocusCallback() { public void onAutoFocus(boolean success, Camera camera) { // TODO Auto-generated method stub if (success) { Log.i(TAG, "autoFocusCallback: success..."); takePhoto(); } else { Log.i(TAG, "autoFocusCallback: fail..."); if (isSupportAutoFocus) { takePhoto(); } } } }; /** * 调整照相的方向,设置拍照相片的方向 */ private void takePhoto() { cameraOrientation = new MyOrientationDetector(mContext); if (mCamera != null) { int orientation = cameraOrientation.getOrientation(); Camera.Parameters cameraParameter = mCamera.getParameters(); cameraParameter.setRotation(90); cameraParameter.set("rotation", 90); if ((orientation >= 45) && (orientation < 135)) { cameraParameter.setRotation(180); cameraParameter.set("rotation", 180); } if ((orientation >= 135) && (orientation < 225)) { cameraParameter.setRotation(270); cameraParameter.set("rotation", 270); } if ((orientation >= 225) && (orientation < 315)) { cameraParameter.setRotation(0); cameraParameter.set("rotation", 0); } mCamera.setParameters(cameraParameter); mCamera.takePicture(shutterCallback, pictureCallback, mPicture); } } private ShutterCallback shutterCallback = new ShutterCallback() { @Override public void onShutter() { // TODO Auto-generated method stub } }; private PictureCallback pictureCallback = new PictureCallback() { @Override public void onPictureTaken(byte[] arg0, Camera arg1) { // TODO Auto-generated method stub } }; private PictureCallback mPicture = new PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { new SavePictureTask().execute(data); mCamera.startPreview();//又一次開始预览 } }; public class SavePictureTask extends AsyncTask<byte[], String, String> { @SuppressLint("SimpleDateFormat") @Override protected String doInBackground(byte[]... params) { File pictureFile = FileUtil.getOutputMediaFile(MEDIA_TYPE_IMAGE, mContext); if (pictureFile == null) { Toast.makeText(mContext, "请插入存储卡!", Toast.LENGTH_SHORT).show(); return null; } try { FileOutputStream fos = new FileOutputStream(pictureFile); fos.write(params[0]); fos.flush(); fos.close(); } catch (FileNotFoundException e) { Log.d(TAG, "File not found: " + e.getMessage()); } catch (IOException e) { Log.d(TAG, "Error accessing file: " + e.getMessage()); } return null; } } @Override public boolean onTouchEvent(MotionEvent event) { reAutoFocus(); return false; } }
文件的布局和调用例如以下:
public class CameraActivity extends Activity{ private CameraPreview mPreview; public static final int MEDIA_TYPE_IMAGE = 1; public static final int MEDIA_TYPE_VIDEO = 2; private String TAG="CameraActivity"; private FrameLayout preview; private ImageButton captureButton; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.camera_preview); mPreview = new CameraPreview(this); preview = (FrameLayout) findViewById(R.id.camera_preview); preview.addView(mPreview); captureButton = (ImageButton) findViewById(R.id.button_capture); captureButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mPreview.takePicture(); } }); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); } }
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000"> <FrameLayout android:id="@+id/camera_preview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" /> <ImageButton android:id="@+id/button_capture" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/camera_icon" android:background="#00000000" /> </LinearLayout>
终于效果例如以下:
注意,里面有一些类没有详细说明,详细能够查看后面的代码下载链接,有什么不明确的能够发邮件,邮件地址为dali_yan@yeah.net
转载请注明出处,http://blog.csdn.net/itbailei/article/details/38822791代码下载地址:下载地址(别走!请留下你的评论) sorry,刚才看了一下在下载的资源的setCameraParms()方法中忘记设置尺寸大小了,自己下载完后加上,
if (setFixPictureWidth != 0 && setFixPictureHeight != 0) { myParam.setPictureSize(setFixPictureWidth , setFixPictureHeight); }对照上面的代码就可以!