天天看點

Android實戰開發:自定義照相機1.權限2.預覽圖像

參考資料:

SurfaceView:http://www.cnblogs.com/xuling/archive/2011/06/06/android.html

android.hardware.Camera2:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0428/2811.html

Camera預設捕獲畫面橫向: http://blog.sina.com.cn/s/blog_777c69930100y7nv.html

感謝以上大神們的的無私分享!

之前在公司寫了一個自定義CameraView,年代久遠,回頭看代碼時居然有點看不懂了。。。

真是好記性不如爛筆頭啊~

趁着年底不忙有時間,再次重寫下Camera,話不多說,開始撸代碼。

1.權限

首先需要在AndroidManifest檔案中配置權限:

<!-- 權限 -->
    <!-- 攝像頭權限 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- 閃光燈權限 -->
    <uses-permission android:name="android.permission.FLASHLIGHT" />
    <!-- 在SDCard中建立與删除檔案權限 -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <!-- 寫入SD卡權限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <!-- 功能 -->
    <!-- 攝像頭功能 -->
    <uses-feature android:name="android.hardware.camera" />
    <!-- 攝像頭自動對焦功能 -->
    <uses-feature android:name="android.hardware.camera.autofocus" />
           

2.預覽圖像

2.1 SurfaceView和SurfaceHolder

關于SurfaceView的詳細介紹可以點這裡:http://www.cnblogs.com/xuling/archive/2011/06/06/android.html

2.1.1 SurfaceView

因為我們需要預覽照相機中的圖像,而這個圖像又是動态變化的,是以必須用到這個SurfaceView。

SurfaceView繼承自View,能夠在非UI線程中在螢幕上繪圖,是以我們可以在預覽圖像的同時進行一些别的操作。

我們把它寫在布局檔案中,使用findViewById獲得它的執行個體即可。

2.1.2 SurfaceHolder

SurfaceHolder相當于是SurfaceView的控制器,用來操縱surface。處理它的Canvas上畫的效果和動畫,控制表面,大小,像素等。

實作SurfaceView需要實作SurfaceHolder.Callback接口,可以自定義一個類繼承SurfaceView并實作這個接口,也可以讓Activity直接實作這個接口,我們這裡使用第二種。

實作SurfaceHolder.Callback接口需要實作三個方法:

public class CameraActivity extends AppCompatActivity implements SurfaceHolder.Callback {
        ...
        mSurfaceHolder.addCallback(this);
        ...
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            //建立時觸發,surfaceView生命周期的開始,在這裡打開相機
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            //surface的大小發生改變時觸發,在這裡預覽圖像
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            //銷毀時觸發,surfaceView生命周期的結束,在這裡關閉相機
        }
    }
           

有了預覽圖像的容器,下面就真正開始使用Camera了。

2.2 Camera

2.2.1 import注意事項

導入包的時候注意是android.hardware.Camera,而不是android.graphics.Camera,不要搞錯了;

hardware中的Camera是控制裝置攝像頭的,graphics中的Camera是對圖像進行處理的。

PS:在android5.0以上,android.hardware.Camera已過時,推薦使用的是android.hardware.Camera2類;但由于Camera2類不向下相容,而且目前安卓手機5.0以上的不多,是以我們還是使用過時的android.hardware.Camera。

關于Camera2類的詳情,點這裡:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0428/2811.html

感謝 泡在網上的日子 的無私分享~

2.2.2 生成預覽圖像

在重寫的surfaceCreated方法中初始化camera,并開啟預覽

@Override
    public void surfaceCreated(SurfaceHolder holder) {
        mCamera = Camera.open();//使用靜态方法open初始化camera對象,預設打開的是後置攝像頭
        try {
            mCamera.setPreviewDisplay(mSurfaceHolder);//設定在surfaceView上顯示預覽
            mCamera.startPreview();//開始預覽
        } catch (IOException e) {
            //在異常處理裡釋放camera并置為null
            mCamera.release();
            mCamera = null;
            e.printStackTrace();
        }
    }
           

可以直接把預覽寫在surfaceCreated裡,也可以寫在surfaceChanged裡

@Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //也可以把預覽寫在這裡
    }
           

在surfaceView被銷毀時,停止預覽并釋放camera對象并置為null

@Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //停止預覽并釋放camera對象并置為null
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }
           

這時候運作程式,點選同意調用攝像頭,我們會發現有圖像了,

可是,為什麼是歪的?

。。。whta the hell。。。

2.2.3 設定預覽方向

之是以是歪的,是因為攝像頭預設捕獲的畫面byte[]是根據橫向來的,而我們的應用是豎向的,

解決辦法是調用setDisplayOrientation來設定PreviewDisplay的方向,效果就是将捕獲的畫面旋轉多少度顯示。

詳情點這裡:http://blog.sina.com.cn/s/blog_777c69930100y7nv.html

是以我們隻要在預覽前調用下setDisplayOrientation這個方法就好了

@Override
    public void surfaceCreated(SurfaceHolder holder) {
        mCamera = Camera.open();
        try {
            mCamera.setPreviewDisplay(mSurfaceHolder);

            //設定預覽偏移90度,一般的裝置都是90,但某些裝置會偏移180
            mCamera.setDisplayOrientation();

            mCamera.startPreview();
        } catch (IOException e) {
            mCamera.release();
            mCamera = null;
            e.printStackTrace();
        }
    }
           

2.2.4 Camera.Parameters 相機參數類

Camera.Parameters是相機參數類,在這裡可以給camera對象設定分辨率,圖檔方向,閃光燈模式等等一些參數,用以實作更豐富的功能。

使用方式如下:

@Override
    public void surfaceCreated(SurfaceHolder holder) {
        mCamera = Camera.open();
        try {
            mCamera.setPreviewDisplay(mSurfaceHolder);
            mCamera.setDisplayOrientation();

            /**Camera.Parameters**/
            Camera.Parameters parameters = mCamera.getParameters();//得到一個已有的(預設的)參數
            parameters.setPreviewSize(, );//設定分辨率,後面有詳細說明
            parameters.setRotation();//設定照相生成的圖檔的方向,後面有詳細說明
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);//設定閃光燈模式為關
            mCamera.setParameters(parameters);//将參數賦給camera

            mCamera.startPreview();
        } catch (IOException e) {
            mCamera.release();
            mCamera = null;
            e.printStackTrace();
        }
    }
           

注意:這裡設定分辨率是不能随便設定的,因為每個裝置所支援的分辨率不一樣,如果設定了裝置不支援的分辨率程式就會崩潰,是以上面我把分辨率寫死是非常不可取的;

可以通過getSupportedPreviewSizes()獲得裝置支援的分辨率list。

List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();//獲得裝置所支援的分辨率清單
        for (int i = ; i < sizeList.size(); i++) {
            //這裡的TAG是一個常量字元串,用來辨別log
            Log.i(TAG, "width:" + sizeList.get(i).width + ",height:" + sizeList.get(i).height);
        }
           

運作這行代碼,我們可以看到如下log

- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
           

在這裡面我們可以獲得所有的支援的分辨率;

但是注意,這裡面的width都比height大,這是因為系統預設的圖像捕捉是橫屏的,而非我們所熟悉的豎屏。

2.2.5 獲得最佳分辨率

我們發現,sizeList的分辨率大小雖然有一定的規律,但是并不是最後一個就是螢幕的分辨率;

比如我測試的手機螢幕分辨率為1920x1080,但是sizeList的最後一個是1680x1248,

是以我們還要對這些資料進行一下篩選,找到最适合螢幕的分辨率。

代碼如下:

/**
     * 獲得最佳分辨率
     * 注意:因為相機預設是橫屏的,是以傳參的時候要注意,width和height都是橫屏下的
     *
     * @param parameters 相機參數對象
     * @param width      期望寬度
     * @param height     期望高度
     * @return
     */
    private int[] getBestResolution(Camera.Parameters parameters, int width, int height) {
        int[] bestResolution = new int[];//int數組,用來存儲最佳寬度和最佳高度
        int bestResolutionWidth = -;//最佳寬度
        int bestResolutionHeight = -;//最佳高度

        List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();//獲得裝置所支援的分辨率清單
        int difference = ;//最小內插補點,初始化市需要設定成一個很大的數

        //周遊sizeList,找出與期望分辨率內插補點最小的分辨率
        for (int i = ; i < sizeList.size(); i++) {
            int differenceWidth = Math.abs(width - sizeList.get(i).width);//求出寬的內插補點
            int differenceHeight = Math.abs(height - sizeList.get(i).height);//求出高的內插補點

            //如果它們兩的和,小于最小內插補點
            if ((differenceWidth + differenceHeight) < difference) {
                difference = (differenceWidth + differenceHeight);//更新最小內插補點
                bestResolutionWidth = sizeList.get(i).width;//指派給最佳寬度
                bestResolutionHeight = sizeList.get(i).height;//指派給最佳高度
            }
        }

        //最後将最佳寬度和最佳高度添加到數組中
        bestResolution[] = bestResolutionWidth;
        bestResolution[] = bestResolutionHeight;
        return bestResolution;//傳回最佳分辨率數組
    }
           

注意:這裡期望寬度和期望高度是為了擴充功能,

一般來說照相機都全屏,直接傳入手機分辨率就可以了

但是如果不全屏呢?

是以就可以在這裡傳入希望的高度和寬度,保證預覽圖像不變形。

修改surfaceCreate方法中的代碼:

@Override
    public void surfaceCreated(SurfaceHolder holder) {
        mCamera = Camera.open();
        try {
            mCamera.setPreviewDisplay(mSurfaceHolder);
            mCamera.setDisplayOrientation();
            Camera.Parameters parameters = mCamera.getParameters();

            /**獲得螢幕分辨率**/
            Display display = this.getWindowManager().getDefaultDisplay();
            Point size = new Point();
            display.getSize(size);
            int screenWidth = size.x;
            int screenHeight = size.y;

            /**獲得最佳分辨率,注意此時要傳的width和height是指橫屏時的,是以要颠倒一下**/
            int[] bestResolution = Utils.getBestResolution(parameters, screenHeight, screenWidth);//Utils是一個工具類,我習慣把操作的方法放在一個工具類中,作為靜态方法使用
            parameters.setPreviewSize(bestResolution[], bestResolution[]);

            parameters.setRotation();
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
            mCamera.setParameters(parameters);
            mCamera.startPreview();
        } catch (IOException e) {
            mCamera.release();
            mCamera = null;
            e.printStackTrace();
        }
    }
           

好了,現在應該就可以看到不變形的圖像了~

如果還變形,請隐藏通知欄,ActionBar和底部的虛拟導航鍵欄,等等等等等。。。

是以這個時候期望寬度和期望高度就有用了,我們可以計算出除去這些系統欄的高度,然後傳入我們算好的值,就可以自動比對出最佳分辨率~

2.2.6 設定自動對焦

現在預覽出來的圖像終于是正常大小了,可還是模模糊糊的,這咋拍?這坑定不能拍啊

是以我們需要實作相機的自動對焦功能,在這裡Android已經給我們封裝的很好了,我們隻需要簡單的調用一下方法就行。

好多手機裡自帶的那種停下來就自動進行對焦的方式我還不太會,就先寫了個觸摸自動對焦

自動對焦需要配置相關權限,和實作Camera.AutoFocusCallback接口

代碼如下:

//重寫onTouchEvent方法
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mCamera.autoFocus(this);//讓Activity實作接口
        return true;
    }

    //實作接口中的方法,自動對焦完成時的回調
    @Override
    public void onAutoFocus(boolean success, Camera camera) {
        //在這裡可以判斷對焦是否成功,進行一些操作
    }
           

自動對焦已經完成,調用Google的東西真是很簡單,我們可以在這裡加個提示框啊什麼的,後面有時間再說。

2.2.7 照相

終于要照相了,T^T

照相調用camera.takePicture方法,

代碼如下:

“`

private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() {//照相動作回調用的pictureCallback

//在這裡可以獲得拍照後的圖檔資料
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {

        //byte[]數組data就是圖檔資料,可以在這裡對圖檔進行處理
        mCamera.startPreview();//恢複預覽
    }
};

//點選事件
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.imgbtnTakePhoto:
            mCamera.takePicture(null, null, pictureCallback);//拍照會停止預覽
            break;
        default:
            break;
    }
}
           

就先寫到這裡吧,有精力再補充。。