由于最近项目需求,需要做一个摄像头预览拍照的功能。写完之后,来写下总结:
1.Android 利用系统Camera来预览拍照,步骤如下:
(1)调用Camera的open()方法打开相机。
(2)调用Camera的getParameters()获取拍照参数,该方法返回一个Cmera.Parameters对象。
(3)调用Camera.Parameters对象对照相的参数进行设置。
(4)调用Camera的setParameters(),并将Camera.Parameters对象作为参数传入,这样就可以对拍照进行参数控制,Android2.3.3以后不用设置。
(5)调用Camerade的startPreview()的方法开始预览取景,在之前需要调用Camera的setPreviewDisplay(SurfaceHolder holder)设置使用哪个SurfaceView来显示取得的图片。
(6)调用Camera的takePicture()方法进行拍照。
(7)程序结束时,要调用Camera的stopPreview()方法停止预览,并且通过Camera.release()来释放资源。
2.预览到的画面是通过SurfaceView进行显示的。然后SurfaceHolder是系统提供的一个用来设置SurfaceView的对象,可以通过SurfaceView对象的getHolder()方法来获得。SurfaceHolder.Callback是Holder用来显示SurfaceView数据的接口,接口有3个方法,分别代表不同的时候。
(1)SurfaceView 被创建的时候
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
(2)SurfaceView 改变的时候
public void surfaceCreated(SurfaceHolder holder)
(3)SurfaceView被销毁的时候
public void surfaceDestroyed(SurfaceHolder holder)
2.1根据以上分析可知其中(1)(2)(3)(4)(5)步可以在SurfaceView created的时候,也就是SurfaceHolder.Callback 接口的回调函数surfaceCreated(SurfaceHolder holder)那里。在设置预览大小和拍照图片大小的时候,如果你的屏幕方向不是固定的话,最好是可以根据屏幕实时的转向来选择不同的长宽,这样才不会出现预览拉伸的情况。还有就是设置预览大小和图片大小的时候,是有限制的,不能随便乱写。
例如以下这些:1920x1080 1280x720 800x480 768x432 720x480 640x480 576x432 480x320 384x288 352x288 320x240 240x160 176x144
@Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub //当surfaceview创建时开启相机 if(camera == null) { camera = Camera.open(); try { //设置参数,开始预览 Camera.Parameters params = camera.getParameters(); params.setPictureFormat(PixelFormat.JPEG);//图片格式 params.setPreviewSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);//预览 params.setPictureSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);//图片大小 params.setJpegQuality(100); camera.setParameters(params);//将参数设置到我的camera camera.setPreviewDisplay(holder);//通过surfaceview显示取景画面 camera.startPreview();//开始预览 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }2.2 当SurfaceView 改变的时候我们要做的就是重新打开预览(即停止预览,然后又重新打开预览)
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub if (holder.getSurface() == null) { return; } try { camera.stopPreview(); } catch (Exception e) { e.printStackTrace(); } try { camera.setPreviewDisplay(holder); camera.startPreview(); } catch (Exception e) { Log.d(TAG, "Error starting camera preview: " + e.getMessage()); } }2.3为了更好的进行内存管理,让app不至于有过多的内存碎片,因此在SurfaceView销毁的时候,我们应该停止预览,release Camera,然后告诉虚拟机回收不用的对象。
@Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub //当surfaceview关闭时,关闭预览并释放资源 camera.stopPreview(); camera.release(); camera = null; holder = null; surface = null; }3.权限问题,由于系统的升级,对于一些敏感权限,系统要求你必须动态获取。而写权限和Camera就属于这类敏感权限,因此必须动态获取
private void requestPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED ||ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.CAMERA}, REQUEST_EXTERNAL_STORAGE); } }权限回调的接口 重写Activity的onRequestPermissionResult()方法即可
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults.length > 0 && requestCode == REQUEST_EXTERNAL_STORAGE && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(MainActivity.this, R.string.granted, Toast.LENGTH_LONG).show(); } else { Toast.makeText(MainActivity.this, R.string.nogradnted, Toast.LENGTH_LONG).show(); } }
AndroidManifest中静态申请的权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" />4.点击拍照按钮进行拍照 调用camera对象的takePicture方法即可。第三个参数是拍完照的时候的数据放回的接口。
camera.autoFocus(new Camera.AutoFocusCallback() {//自动对焦 @Override public void onAutoFocus(boolean success, Camera camera) { // TODO Auto-generated method stub if(success) { camera.takePicture(null, null, picture_callback);//将拍摄到的照片给自定义的对象 } } });在存储照片的时候,为了不影响主线程的流畅性,应该将写入的方法放到子线程中去。带写入完成的时候,插入到系统图库即可。
//创建jpeg图片回调数据对象 Camera.PictureCallback picture_callback = new Camera.PictureCallback() { @Override public void onPictureTaken(final byte[] data, Camera camera) { //将保存图片的放到子线程中去,别影响主线程 new Thread(new Runnable() { @Override public void run() { try { Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); //自定义文件保存路径 以拍摄时间区分命名 filepath = "/sdcard/myCamera/"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+ ".jpg"; final File file = new File(filepath); if (!file.exists()) { file.getParentFile().mkdir(); } BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);//将图片压缩的流里面 bos.flush();// 刷新此缓冲区的输出流 bos.close();// 关闭此输出流并释放与此流有关的所有系统资源 bitmap.recycle();//回收bitmap空间 runOnUiThread(new Runnable() { @Override public void run() { try { //图片插入到系统图库中 MediaStore.Images.Media.insertImage(getContentResolver(), filepath, file.getName(), null); } catch (FileNotFoundException e) { e.printStackTrace(); } Toast.makeText(MainActivity.this, "照片保存成功" + filepath, Toast.LENGTH_SHORT).show(); } }); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }).start(); camera.stopPreview();//关闭预览 处理数据 camera.startPreview();//数据处理完后继续开始预览 } };5.切换摄像头,一般手机都是默认有前后摄像头的,
//切换前后摄像头 int cameraCount = 0; Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); cameraCount = Camera.getNumberOfCameras();//得到摄像头的个数 for(int i = 0; i < cameraCount; i++) { Camera.getCameraInfo(i, cameraInfo);//得到每一个摄像头的信息 if(cameraPosition == 1) { //现在是后置,变更为前置 if(cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {//代表摄像头的方位,CAMERA_FACING_FRONT前置 CAMERA_FACING_BACK后置 camera.stopPreview();//停掉原来摄像头的预览 camera.release();//释放资源 camera = null;//取消原来摄像头 camera = Camera.open(i);//打开当前选中的摄像头 try { camera.setPreviewDisplay(holder);//通过surfaceview显示取景画面 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } camera.startPreview();//开始预览 cameraPosition = 0; break; } } else { //现在是前置, 变更为后置 if(cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {/ // /代表摄像头的方位,CAMERA_FACING_FRONT前置 // CAMERA_FACING_BACK后置 camera.stopPreview();//停掉原来摄像头的预览 camera.release();//释放资源 camera = null;//取消原来摄像头 camera = Camera.open(i);//打开当前选中的摄像头 try { camera.setPreviewDisplay(holder);//通过surfaceview显示取景画面 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } camera.startPreview();//开始预览 cameraPosition = 1; break; } } }6 demo效果图
7完整代码
布局代码:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <SurfaceView android:layout_centerInParent="true" android:id="@+id/cp_surface" android:layout_width="match_parent" android:layout_height="match_parent" /> <ImageView android:layout_alignLeft="@+id/cp_surface" android:layout_alignTop="@+id/cp_surface" android:id="@+id/iv_back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" android:src="@mipmap/back"/> <ImageView android:layout_alignRight="@+id/cp_surface" android:layout_alignTop="@+id/cp_surface" android:id="@+id/iv_switch_camera" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" android:src="@mipmap/swap"/> <ImageView android:layout_alignBottom="@+id/cp_surface" android:layout_centerHorizontal="true" android:layout_margin="10dp" android:id="@+id/iv_shutter" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/take_photo"/> </RelativeLayout>Activity代码:
import android.Manifest; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.PixelFormat; import android.hardware.Camera; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.ImageView; import android.widget.Toast; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback{ private static final String TAG = "MainActivity"; private static final int REQUEST_EXTERNAL_STORAGE = 10086; private static final int PREVIEW_WIDTH = 1920; private static final int PREVIEW_HEIGHT = 1080; private ImageView iv_back, iv_switch_camera;//返回和切换前后置摄像头 private SurfaceView surface; private ImageView iv_shutter;//快门 private SurfaceHolder holder; private Camera camera;//声明相机 private String filepath = "";//照片保存路径 private int cameraPosition = 1;//0代表前置摄像头,1代表后置摄像头 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE);//没有标题 this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//设置全屏 this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);//拍照过程屏幕一直处于高亮 //设置手机屏幕朝向,一共有7种 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); //SCREEN_ORIENTATION_BEHIND: 继承Activity堆栈中当前Activity下面的那个Activity的方向 //SCREEN_ORIENTATION_LANDSCAPE: 横屏(风景照) ,显示时宽度大于高度 //SCREEN_ORIENTATION_PORTRAIT: 竖屏 (肖像照) , 显示时高度大于宽度 //SCREEN_ORIENTATION_SENSOR 由重力感应器来决定屏幕的朝向,它取决于用户如何持有设备,当设备被旋转时方向会随之在横屏与竖屏之间变化 //SCREEN_ORIENTATION_NOSENSOR: 忽略物理感应器——即显示方向与物理感应器无关,不管用户如何旋转设备显示方向都不会随着改变("unspecified"设置除外) //SCREEN_ORIENTATION_UNSPECIFIED: 未指定,此为默认值,由Android系统自己选择适当的方向,选择策略视具体设备的配置情况而定,因此不同的设备会有不同的方向选择 //SCREEN_ORIENTATION_USER: 用户当前的首选方向 setContentView(R.layout.activity_main); initView(); setListener(); requestPermission(); } private void initView() { iv_back = (ImageView) findViewById(R.id.iv_back); iv_switch_camera = (ImageView) findViewById(R.id.iv_switch_camera); surface = (SurfaceView) findViewById(R.id.cp_surface); iv_shutter = (ImageView) findViewById(R.id.iv_shutter); holder = surface.getHolder();//获得句柄 holder.addCallback(this);//添加回调 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//surfaceview不维护自己的缓冲区,等待屏幕渲染引擎将内容推送到用户面前 } private void setListener() { //设置监听 iv_back.setOnClickListener(listener); iv_switch_camera.setOnClickListener(listener); iv_shutter.setOnClickListener(listener); } private void requestPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED ||ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.CAMERA}, REQUEST_EXTERNAL_STORAGE); } } //响应点击事件 View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub switch (v.getId()) { case R.id.iv_back: //返回 MainActivity.this.finish(); break; case R.id.iv_switch_camera: //切换前后摄像头 int cameraCount = 0; Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); cameraCount = Camera.getNumberOfCameras();//得到摄像头的个数 for(int i = 0; i < cameraCount; i++) { Camera.getCameraInfo(i, cameraInfo);//得到每一个摄像头的信息 if(cameraPosition == 1) { //现在是后置,变更为前置 if(cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {//代表摄像头的方位,CAMERA_FACING_FRONT前置 CAMERA_FACING_BACK后置 camera.stopPreview();//停掉原来摄像头的预览 camera.release();//释放资源 camera = null;//取消原来摄像头 camera = Camera.open(i);//打开当前选中的摄像头 try { camera.setPreviewDisplay(holder);//通过surfaceview显示取景画面 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } camera.startPreview();//开始预览 cameraPosition = 0; break; } } else { //现在是前置, 变更为后置 if(cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {//代表摄像头的方位,CAMERA_FACING_FRONT前置 CAMERA_FACING_BACK后置 camera.stopPreview();//停掉原来摄像头的预览 camera.release();//释放资源 camera = null;//取消原来摄像头 camera = Camera.open(i);//打开当前选中的摄像头 try { camera.setPreviewDisplay(holder);//通过surfaceview显示取景画面 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } camera.startPreview();//开始预览 cameraPosition = 1; break; } } } break; case R.id.iv_shutter: //快门 camera.autoFocus(new Camera.AutoFocusCallback() {//自动对焦 @Override public void onAutoFocus(boolean success, Camera camera) { // TODO Auto-generated method stub if(success) { camera.takePicture(null, null, picture_callback);//将拍摄到的照片给自定义的对象 } } }); break; } } }; /*surfaceHolder他是系统提供的一个用来设置surfaceView的一个对象,而它通过surfaceView.getHolder()这个方法来获得。 Camera提供一个setPreviewDisplay(SurfaceHolder)的方法来连接*/ //SurfaceHolder.Callback,这是个holder用来显示surfaceView 数据的接口,他必须实现以下3个方法 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub if (holder.getSurface() == null) { return; } try { camera.stopPreview(); } catch (Exception e) { e.printStackTrace(); } try { camera.setPreviewDisplay(holder); camera.startPreview(); } catch (Exception e) { Log.d(TAG, "Error starting camera preview: " + e.getMessage()); } } @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub //当surfaceview创建时开启相机 if(camera == null) { camera = Camera.open(); try { //设置参数,开始预览 Camera.Parameters params = camera.getParameters(); params.setPictureFormat(PixelFormat.JPEG);//图片格式 params.setPreviewSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);//预览 params.setPictureSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);//图片大小 params.setJpegQuality(100); camera.setParameters(params);//将参数设置到我的camera camera.setPreviewDisplay(holder);//通过surfaceview显示取景画面 camera.startPreview();//开始预览 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub //当surfaceview关闭时,关闭预览并释放资源 camera.stopPreview(); camera.release(); camera = null; holder = null; surface = null; } //创建jpeg图片回调数据对象 Camera.PictureCallback picture_callback = new Camera.PictureCallback() { @Override public void onPictureTaken(final byte[] data, Camera camera) { //将保存图片的放到子线程中去,别影响主线程 new Thread(new Runnable() { @Override public void run() { try { Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); //自定义文件保存路径 以拍摄时间区分命名 filepath = "/sdcard/myCamera/"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+ ".jpg"; final File file = new File(filepath); if (!file.exists()) { file.getParentFile().mkdir(); } BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);//将图片压缩的流里面 bos.flush();// 刷新此缓冲区的输出流 bos.close();// 关闭此输出流并释放与此流有关的所有系统资源 bitmap.recycle();//回收bitmap空间 runOnUiThread(new Runnable() { @Override public void run() { try { //图片插入到系统图库中 MediaStore.Images.Media.insertImage(getContentResolver(), filepath, file.getName(), null); } catch (FileNotFoundException e) { e.printStackTrace(); } Toast.makeText(MainActivity.this, "照片保存成功" + filepath, Toast.LENGTH_SHORT).show(); } }); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }).start(); camera.stopPreview();//关闭预览 处理数据 camera.startPreview();//数据处理完后继续开始预览 } }; @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults.length > 0 && requestCode == REQUEST_EXTERNAL_STORAGE && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(MainActivity.this, R.string.granted, Toast.LENGTH_LONG).show(); } else { Toast.makeText(MainActivity.this, R.string.nogradnted, Toast.LENGTH_LONG).show(); } } }参考链接:http://blog.csdn.net/gf771115/article/details/19438409
以上就是摄像头预览拍照的所有介绍。希望对你有所帮助。也感谢其他博主的分享