您的位置:首页 > 移动开发 > Android开发

android Camera摄像头-Surface view 预览拍照 并将拍的照片插入到系统图库

2017-09-22 09:42 591 查看
由于最近项目需求,需要做一个摄像头预览拍照的功能。写完之后,来写下总结:

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)
1af88
{
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

以上就是摄像头预览拍照的所有介绍。希望对你有所帮助。也感谢其他博主的分享
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐