1. 程式人生 > >android Camera攝像頭-Surface view 預覽拍照 並將拍的照片插入到系統圖庫

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

以上就是攝像頭預覽拍照的所有介紹。希望對你有所幫助。也感謝其他博主的分享