android Camera攝像頭-Surface view 預覽拍照 並將拍的照片插入到系統圖庫
由於最近專案需求,需要做一個攝像頭預覽拍照的功能。寫完之後,來寫下總結:
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
以上就是攝像頭預覽拍照的所有介紹。希望對你有所幫助。也感謝其他博主的分享