1. 程式人生 > >簡單談談android自定義相機的實現(上 android.hardware.Camera)

簡單談談android自定義相機的實現(上 android.hardware.Camera)

通常情況下,呼叫android系統相機基本上可以滿足拍照的需求,而自定義相機的需求一般來自於開發自己的相機應用,今天我們來簡單聊聊android自定義相機的實現,限於篇幅,我們上篇只討論android.hardware.Camera,下篇我會和大家一起討論一下android.hardware.Camera2

  1. 基礎介紹

首先簡單看一下用到的類有哪些:

類名 介紹 註釋
android 5.0 之前控制相機硬體是通過這個Camera類實現的 deprecated in API level 21.
控制相機的引數,如對焦,閃光燈等 deprecated in API level 21.
專注於繪製應用層內嵌浮層的類,這裡我們用它來預覽鏡頭的取景 \
持有一個展示的surface的抽象介面。允許你控制surface的尺寸和格式,編輯surface中的畫素和監聽surface的改變。 \

2. demo實現

  1. 新增許可權
  2. 簡單佈局
  3. 開啟相機
  4. 設定預覽
  5. 拍照及儲存
  6. 釋放資源

新增許可權

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name
="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

camera的官方介面文件中,給出了控制camera的10個步驟,很詳細,我們這邊側重於實現。

簡單佈局


一個簡單的介面,一個拍照鍵Button,一個FrameLayout用於填充Surface
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/camera_container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".Camera.CameraActivity">
<FrameLayout android:id="@+id/camera_preview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentLeft="true" android:layout_alignParentTop="true"/> <LinearLayout android:id="@+id/control" android:layout_width="match_parent" android:layout_height="112dp" android:layout_alignParentBottom="true" android:background="@color/control_background" android:gravity="center" android:orientation="horizontal"> <Button android:id="@+id/camera_take_photo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="@string/picture" /> </LinearLayout> </RelativeLayout>

demo圖

**開啟相機**

這裡包括了檢測系統相機狀態,指定開啟具體的相機裝置,處理異常等

/**
* 初始化相機
*/
private void initCamera() {
   if(!getPackageManager().hasSystemFeature(
       PackageManager.FEATURE_CAMERA)) {
     //TODO 未檢測到系統的相機
} else {
     //獲取cameraId ,在api8及以前,直接呼叫Camera.open()方法會開啟後置攝像頭
     //獲取系統後置攝像頭的id(工具方法,後面附程式碼)
     int cameraId = CameraUtil.findBackFacingCamera();
     if (!CameraUtil.isCameraIdValid(cameraId)) {
          //TODO 檢測camera id無效
     } else {
         //是否可以安全開啟相機
         if (safeCameraOpen(cameraId)) {
           mCamera.startPreview();  //開啟相機預覽
           mPreview.setCamera(mCamera);
         } else {             
             //TODO 無法安全開啟相機    
         }
     }
 }
//開啟相機的操作延遲到onResume()方法裡面去執行,這樣可以使得程式碼更容易重用,還能保持控制流程更為簡單。當然也可以另起執行緒處理
@Override
public void onResume() {
    super.onResume();
    initCamera();
}
附上獲取相機id的工具方法:
public final class CameraUtil {

    public static final int INVALID_CAMERA_ID = -1;

    /**
     * 獲取前置攝像頭id
     * @return
     */
    public static int findFrontFacingCamera() {
        int cameraId = INVALID_CAMERA_ID;
        // Search for the front facing camera
        int numberOfCameras = Camera.getNumberOfCameras();
        for (int i = 0; i < numberOfCameras; i++) {
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(i, info);
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                Log.d("CameraUtil", "Camera found");
                cameraId = i;
                break;
            }
        }
        return cameraId;
    }

    /**
     * 獲取後置攝像頭id
     * @return
     */
    public static int findBackFacingCamera() {
        int cameraId = INVALID_CAMERA_ID;
        // Search for the front facing camera
        int numberOfCameras = Camera.getNumberOfCameras();
        for (int i = 0; i < numberOfCameras; i++) {
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(i, info);
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                Log.d("CameraUtil", "Camera found");
                cameraId = i;
                break;
            }
        }
        return cameraId;
    }

    public static boolean isCameraIdValid(int cameraId) {
        return cameraId != INVALID_CAMERA_ID;
    }

}
處理開啟相機時可能出現的異常:
    /**
     * 獲取Camera,並加入開啟檢測
     *
     * @param id 相機id
     * @return
     */
    private boolean safeCameraOpen(int id) {
        boolean qOpened = false;
        try {
            releaseCameraAndPreview();
            mCamera = Camera.open(id);
            qOpened = (mCamera != null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return qOpened;
    }
這樣不出意外,我們成功的打開了相機 —

**設定取景的預覽**

這裡包括了設定相機引數,設定相機方向,初始化SurfaceHolder從而來setPreviewDisplay(SurfaceHolder) ,完成這一步,即可實現取景的預覽。

一般的,我們可以在佈局中放置一個預設的SurfaceView,也可以通過addView()方法將SurfaceView例項新增到指定的容器中。
這裡我們通過繼承SurfaceView,派生一個自定義的CameraPreview,同時實現SurfaceHolder.Callback介面便於我們對surface進行控制。

public class CameraPreview extends SurfaceView implements           SurfaceHolder.Callback {

    private static final String TAG = "TAG";

    /**
     * 控制相機方向
     */
    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();

    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    private SurfaceHolder mHolder;
    private Camera mCamera;

    //持有Activity引用,為了獲取螢幕方向,改成內部類會比較好
    private Activity mActivity;

    public CameraPreview(Activity activity) {
        super(activity);
        mActivity = activity;
        mHolder = getHolder();
        mHolder.addCallback(this);
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            //API 11及以後廢棄,需要時自動配置
           mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }
    }

    public void setCamera(Camera camera) {
        mCamera = camera;
    }

    /**
     * 重新整理相機
     */
    private void refreshCamera() {
        if (mCamera != null) {
            requestLayout();
            //獲取當前手機螢幕方向
            int rotation = mActivity.getWindowManager()
                .getDefaultDisplay().getRotation();
            //調整相機方向            
            mCamera.setDisplayOrientation(
            ORIENTATIONS.get(rotation));
            // 設定相機引數
            mCamera.setParameters(settingParameters());
        }
    }

    /**
     * 配置相機引數
     * @return
     */
    private Camera.Parameters settingParameters() {
        // 獲取相機引數
        Camera.Parameters params = mCamera.getParameters();
        List<String> focusModes = params.getSupportedFocusModes();
        //設定持續的對焦模式
        if (focusModes.contains(
        Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        }

        //設定閃光燈自動開啟
        if (focusModes.contains(Camera.Parameters.FLASH_MODE_AUTO)) {
            params.setFocusMode(Camera.Parameters.FLASH_MODE_AUTO);
        }

        return params;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            if(mCamera != null) {
                //surface建立,設定預覽SurfaceHolder
                mCamera.setPreviewDisplay(holder);
                //開啟預覽
                mCamera.startPreview();
            }
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (mHolder.getSurface() == null) {
            // preview surface does not exist
            return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e) {
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here
        refreshCamera();
        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e) {
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

CameraPreview裡面涉及到預覽主要的邏輯,一些如對焦模式或閃光燈模式等相機引數,大家可以自己參照API文件。接下來只要在onCreate()方法裡,將我們的SurfaceView加入我們設定好的佈局容器中即可。

mPreview = new CameraPreview(this);
FrameLayout vPreview = (FrameLayout) findViewById(R.id.camera_preview);
vPreview.addView(mPreview);

實現拍照


設定按鈕的點選事件的程式碼就不貼出來了,主要說說如何拍照,以及將照片儲存在檔案中。

後面兩個回撥分別發生在原始的照片資料/壓縮的jpeg照片資料可用時,api5之後,額外提供了一個4個回撥引數的方法,大家自己看文件就好。這裡我們簡化處理,只提供最後一個回撥:

//觸發一個非同步的圖片捕獲回撥
mCamera.takePicture(null, null, this);
...
@Override
public void onPictureTaken(final byte[] data, Camera camera) {
    File pictureFile = ...; //自行建立一個file檔案用於儲存照片
    OutputStream output = null;
    try {
        output = new FileOutputStream(pictureFile);
        output.write(data);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (output != null) {
            try {
                output.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //儲存成功後的處理
    resetCamera();
}

其實很簡單,只需要將位元組資料寫進檔案裡即可,如果儲存完圖片後想繼續拍照,這時候就需要重置一下相機,因為拍照完,取景預覽預設定格在取景頁,提供一下resetCamera()方法,供大家參考

/**
* 重置相機
*/
private void resetCamera() {
   mCamera.startPreview();
   mPreview.setCamera(mCamera);
}

相機資源的釋放

/**
* 釋放相機和預覽
*/
private void releaseCameraAndPreview() {
   mPreview.setCamera(null);
   if (mCamera != null) {
       mCamera.release();
       mCamera = null;
   }
}

如果我們開啟相機是activity的onResume()方法中,那麼我們可以在onPause()方法中釋放相機和預覽資源

@Override
protected void onPause() {
   super.onPause();
   releaseCameraAndPreview();
}

補充


(1). 一般情況下,我們無需自己實現拍照。不過有些特殊的需求還必須自己控制拍照過程。
例如,我們想獲取照片拍攝的精準時間,一般我們只能拿到照片的updateTime。即使通過解析照片檔案的方式獲取到createTime,也和照片實際的拍攝時間有誤差。原因很簡單,大家看第三步
takePicture (Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg)方法的回撥,createTime實際上是位元組資料寫入file中的時刻。舉個例子,我們設定微信頭像,選擇拍照,微信會調取系統相機,這時,我們點選拍照,成功,進入系統提示是否儲存的頁面,如圖:
微信拍照

大家看,這時圖片還是以位元組的形式存在,只有當用戶點選儲存,系統才會儲存圖片。所以檔案的建立時間其實並不是拍照的那一刻(雖然也差不多)。所以自己控制camera拍照的話就可以拿到相對精準的照片拍攝時間。

留個坑:android中如何獲取照片(檔案)的建立時間?

目測:基於linux的android也只能拿到lastModified time

(2). SurfaceView 生命週期的控制
還是上一個圖,例如我們拍照後進入圖中狀態,這時鎖屏或者進入後臺(觸發Activity的onPause()方法),然後我們再返回應用,此時我們會發現,SurfaceView原本固定的取景完全恢復了。首先我們想到的處理方法是設定狀態機,
定義整個拍照過程的不同狀態,如:

    /**
     * 預覽中
     */
    private static final int STATE_PREVIEW = 0;
    /**
     * 等待儲存
     */
    private static final int STATE_WAITING_SAVE = 1;
    /**
     * 儲存中
     */
    private static final int STATE_SAVING = 2;

在onResume()和onPause()方法中,針對不同的狀態,控制初始化/釋放的過程。
但是,SurfaceView預設的生命週期和activity也是相關的,所以如果希望在上述操作中保持SurfaceView的狀態,需要額外一些操作。
再留一個坑:SurfaceView生命週期與Activity的生命週期

(3). 拍照方向
android相機感測器預設是橫向的,當豎屏拍照時,照片預設是旋轉90度的
第三個坑:照片旋轉了如何處理?

相關推薦

Android定義相機實現N連拍

/** * 描述:自動連續拍照 * 開發者:開發者的樂趣JRT * 建立時間:2017-3-15 19:16 * CSDN地址:http://blog.csdn.net/Jiang_Rong_Tao/article * E-mail:[email protected] **/ publi

Android定義照相機實現拍照、儲存到SD卡,利用Bundle在Acitivity交換資料

Android自定義照相機實現 近期小巫在學校有一個創新專案,也不是最近,是一個拖了很久的專案,之前一直沒有去搞,最近因為要中期檢查,搞得我跟小組成員一陣忙活,其實開發一款照相機軟體並不太難,下面就是通過自定義的方式來實現手機照相的功能。 建立一個專案:FingerTake

Android定義相機實現拍照、預覽、顯示、

自定義相機拍照並存放到本地,可以預覽,用okHttp上傳到伺服器 用法 1.點選登入進入到拍照頁面 2.拍照後進入到上傳介面,需要在Constant中修改BASE_URL為自己伺服器圖片上傳地

Android定義View實戰會波動的View

今天寫這篇部落格的目的,主要是幫助自己總結一下今天學習到的自定義View的相關知識,如果順便能夠幫助大家一點點,那我也感覺很開心。 首先,自定義的一般步驟是: 1.建立自定義View,繼承系統自帶的View,並重寫其相關構造方法; 2.在Values下面新

簡單談談android定義相機實現 android.hardware.Camera

通常情況下,呼叫android系統相機基本上可以滿足拍照的需求,而自定義相機的需求一般來自於開發自己的相機應用,今天我們來簡單聊聊android自定義相機的實現,限於篇幅,我們上篇只討論android.hardware.Camera,下篇我會和大家一起討論一下a

Android定義相機Camera

Time:2018/06/21  因為專案需求,需要實現跟小猿搜題的類似的功能,系統相機直接就被排除了,原本打算是找個一個demo,改吧改吧就直接用了,找的過程中發現程式碼太舊了,目前6.0以上的系統很多不支援,然後按照demo的邏輯,就寫一個相

android定義相機帶邊框和按鈕

前兩個月專案要求不能呼叫系統的相機,那就只能用自定義的了,查了一些資料,自己再研究了一下,自定義的相機還是有點複雜的,佈局和程式碼中都要用到一個重要的SurfaceView。 一、建立佈局,佈局的背景框可以讓美工給出,這裡姑且就是一個藍色的邊框,然後下面有三個按鈕,我里布局檔案activit

android 定義ListView實現下拉重新整理、分頁載入、點選事件——定義控制元件學習

package com.example.administrator.customerpulldownrefreshandpageload; import android.content.Context; import android.os.Handler; import android.os.Message

Android定義相機 —— 錄影

前面我們已經大致完成了自定義相機的拍照功能,接下來,我們來實現一下錄影的功能。 1、錄影功能簡介 錄影功能的是相對比較簡單,因為步驟是很固定的,google給我們提供的api文件中說的也比較詳細,這裡我們主要用到 MediaRecorder 這個類。

Android定義View實現簡單的折線圖、柱狀圖

首先說第一個柱狀圖,實現很簡單。一個自定義View,重現裡面的OnDraw方法。然後利用paint,canvas繪製帶填充的長方形即可。每個長方形的X軸平方View的x軸即可,長方形的高度通過簡單的計算即可得到。下面上柱狀圖程式碼 package com.hrules.

Android定義介面卡---實現簡單檔案管理器

一、介面卡Adapter         現實生活中的介面卡就是一種“轉化器”,將兩個不相容的事物做一個連線。Android在檢視顯示和後臺資料上使用介面卡,顧名思義,就是把一些資料給變得適當,適合以便於在View上顯示。可以看作是介面資料繫結的一種理解。它所操縱的資料一般

Android 定義相機獲取照片螢幕適配

  1.在應用程式中自定義相機拍照的大體步驟如下: 1.檢測和訪問相機:檢測裝置是否支援拍照,然後發出訪問裝置相機請求。        2.建立一個預覽類:建立一個繼承自類SurfaceView和實現介面SurfaceHolder介面的相機預覽類,這個類用來預覽從相機得到

Android圓形圖片不求人,定義View實現BitmapShader使用

在很多APP當中,圓形的圖片是必不可少的元素,美觀大方。本文將帶領讀者去實現一個圓形圖片自定View,力求只用一個Java類來完成這件事情。 一、先上效果圖 二、實現思路 在定義View 的onMeasure()方法裡設定View的寬高相等,應該取寬高中的最小值。

android定義View,實現折線圖

效果圖: LineChartView類: public class LineChartView extends View { private int width; private int height; private float maxVal

android定義相機、連續自動聚焦、點選觸控聚焦、變焦、拍照後定義裁剪、旋轉

最近做了一個圖片識別、以及搜尋的小專案,其中有一個模組是拍照以及拍照後對圖片進行剪下,開始用的系統的相機和裁剪,由於系統的相機和裁剪多出了一些不必要的步驟和啟動慢等等帶給使用者的體驗不好,故自己寫了一個,下面給大家簡要介紹下: 自定義相機:定義SurfaceView得到Su

關於Android定義相機進行拍照小米手機出現異常的原因

前幾天專案需要自定義相機,於是就到網上百度了一下,看了一下程式碼,自己也寫了一下。中間遇到小米手機就是不行一直setParameterFailed。最後發現我的問題出在 parameters.setPreviewFra

android定義Button樣式清晰簡單

最近在學習一下簡單的控制元件,如Button,TextView等...但系統提供的樣式大多都滿足不了,所以我們需要自己來自定義樣式來滿足自己的需求。 下面跟大家分享一下最近學到的一種自定義方式,以下用Button按鈕來做例子: 步驟1: 在res/drawable資料夾

我的Android進階之旅------>Android定義View實現帶數字的進度條NumberProgressBar

今天在Github上面看到一個來自於 daimajia所寫的關於Android自定義View實現帶數字的進度條(NumberProgressBar)的精彩案例,在這裡分享給大家一起來學習學習!同時感謝daimajia的開源奉獻! 第一步、效果展

Android定義相機超詳細講解

了解 catch 實現 4.4 required form 需要 eset 自己 Android自定義相機超詳細講解 轉載請標明出處: http://blog.csdn.net/vinicolor/article/details/49642861; 由於網上關於Andr

Android -- 定義view實現keep歡迎頁倒計時效果

super onfinish -m use new getc awt ttr alt 1,最近打開keep的app的時候,發現它的歡迎頁面的倒計時效果還不錯,所以打算自己來寫寫,然後就有了這篇文章。 2,還是老規矩,先看一下我們今天實現的效果   相較於我們常見的倒計時