使用 PreviewView 來展現相機預覽

顯示相機預覽內容是每一個相機類應用都會包含的功能,想要完美實現這個卻並不是易事。緣由是,在某些特別極端狀況下 camera2 API 的使用會變得很複雜,並且在不一樣設備上的行爲還會有所不一樣。還好,Jetpack CameraX 庫PreviewView 能夠幫助您解決這一問題。經過在各類 Android 設備上提供開發者友好、一致且穩定的 API,使得展現相機的預覽變得再也不困難。html

PreviewView 的介紹

PreviewView 是一個能夠顯示相機畫面的自定義 View,它被構建的初衷即是下降開發者們在設置和處理相機所使用的預覽畫面 (preview surface) 的難度。java

若是您須要在應用中提供展現相機畫面的基本功能,使用 PreviewView 是最推薦的作法,它有如下幾個優勢:android

  • 使用簡單PreviewView 是一個 View,它經過管理 Preview 用例所使用的 Surface 來實現將相機捕捉到的畫面展現在界面佈局中的所有功能;
  • 代碼輕量 : PreviewView 只專一於實現相機畫面預覽功能。它全部內部資源都致力於對相機預覽畫面的展現,以及在相機使用過程當中對預覽畫面 (preview surface) 進行管理。這樣的關注點分離使得 PreviewView 的代碼可以保持簡潔;
  • 支持全面 : PreviewView 解決了在屏幕上展現相機畫面過程當中最難處理的部分,包括對畫面寬高比、縮放和旋轉的處理。不一樣的設備會致使不一致的行爲,包括設備、屏幕尺寸、攝像頭硬件支持水平,還會須要適配諸如分屏模式、不一樣鎖定方向和可動態調節尺寸的展現窗口等顯示模式,爲了解決這些問題並在多種設備上提供無縫體驗,PreviewView 還作了一些兼容性的處理。

PreviewView 的實現模式

PreviewViewFrameLayout 的子類,它會使用 SurfaceView 或者 TextureView 展現來自相機捕捉到的畫面。一旦相機準備好,就會建立一個預覽畫面 (preview surface) 的實例,並在相機使用過程當中儘可能持有該實例,若是相機還在工做中卻提早釋放了所持有的預覽畫面 (preview surface) 實例,就會從新建立一個。git

當涉及到諸如功耗和響應時間這些關鍵指標時,SurfaceView 的表現通常都比 TextureView 要好,這也是爲何 PreviewView 會將 SurfaceView 做爲默認實現模式的緣由。然而,一些設備 (主要是一些 舊版設備) 會在預覽畫面 (preview surface) 過早釋放時出現閃退的狀況。惋惜的是,使用 SurfaceView 時沒法控制什麼時候對畫面 (surface) 進行釋放,由於這是由 View 層級結構所控制的。所以在這些設備上,PreviewView 只能使用 TextureView 做爲實現模式。另外在須要對相機預覽界面進行旋轉、改變透明度或加入動畫的狀況下,您也應該強制 PreviewView 使用 TextureView 做爲實現模式。github

您能夠經過調用 PreviewView.setPreferredImplementationMode(ImplementationMode)) 並設置 ImplementationMode 參數爲 SURFACE_VIEWTEXTURE_VIEW 來更改 PreviewView 的實現模式。當首選模式設置爲 SURFACE_VIEW 時,PreviewView 會盡量遵循您的設置 (使用 SurfaceView);而當首選模式設置爲 TEXTURE_VIEW 時,PreviewView 會確保一直使用 TEXTURE_VIEW 模式。api

⚠️  在開始使用 PreviewView 以前,請務必經過調用 Preview.setSurfaceProvider(PreviewView.createSurfaceProvider())) 來設置您想要的實現模式。app

下面介紹如何設置 PreviewView 的實現模式:ide

// 進行相機畫面預覽以前,設置想要的實現模式
previewView.preferredImplementationMode = ImplementationMode.SURFACE_VIEW

// 將 previewView 設置到 preview 用例中來開始進行相機畫面預覽
preview.setSurfaceProvider(previewView.createSurfaceProvider(cameraInfo))

PreviewView - Preview

PreviewView 經過處理建立 Preview 用例所須要的 SurfaceProvider,來啓動一個預覽畫面的數據流。SurfaceProvider 會準備好須要提供給相機的 Surface,用來對預覽畫面的數據流進行展現,並負責在必要時從新建立 SurfacePreviewView.createSurfaceProvider(CameraInfo)) 接收一個 nullable 的 CameraInfo 實例。PreviewView 會結合所傳入的 CameraInfo 參數,以及您所設定的實現模式和當前相機具有功能,來決定內部如何進行功能上的實現。若是您所傳入的 CameraInfo 是一個 null,那 PreviewView 會使用 TextureView 做爲實現模式,由於它沒法肯定所選的相機若使用 SurfaceView 是否能夠正常工做。函數

一旦您建立好了 Preview 用例和一些別的所須要的 實例 後,將它們綁定至 LifecycleOwner,使用所綁定的相機的 CameraInfo 來建立 SurfaceProvider,再將其綁定至 Preview 用例,調用 Preview.setSurfaceProvider(SurfaceProvider) 來啓動預覽畫面數據流。佈局

下面的例子展現瞭如何將 PreviewView 綁定至 Preview 來開啓預覽畫面數據流:

// 建立 preview 用例
val preview = Preview.Builder().build()

// 將 preview 和其餘須要的用例綁定到 lifecycle 中
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageAnalysis, imageCapture)

// 使用所綁定相機的 cameraInfo 建立 surfaceProvider
val surfaceProvider = previewView.createSurfaceProvider(camera.cameraInfo)

// 將 surfaceProvider 綁定至 preview 用例來啓動預覽
preview.setSurfaceProvider(surfaceProvider)

PreviewView - 縮放 (scale) 類型

PreviewView 提供了一個 API,經過它可讓您控制預覽畫面的樣式是怎樣的 (how) 和在父級視圖中的位置 (where):

  • how  決定將預覽畫面放置於 ( FIT ) 父級視圖中仍是填充於 ( FILL ) 父級視圖中;
  • where  決定預覽畫面相對於父級視圖來講,是左上方對齊 ( START ),居中對齊 ( CENTER ) 仍是右下方對齊 ( END )。

"how" 和 "where" 所組合出來的結果,表明了 PreviewView 支持的縮放 (scale) 類型,包括 FIT_START、FIT_CENTER、FIT_END、FILL_START、FILL_CENTER and FILL_END。其中最經常使用的是 FIT_CENTER 和 FILL_CENTER,前者將預覽界面在保證寬高比的前提下進行縮放而後居中,後者不會進行縮放,保證居中可是可能會致使畫面被裁剪。

有兩種方法能夠設置縮放 (scale) 類型: 

  • 經過在 XML 佈局文件中設置 PreviewView 的 scaleType 屬性來實現,如如下示例所示:
<androidx.camera.view.PreviewView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:scaleType="fitEnd" />
previewView.setScaleType(ScaleType.FIT_CENTER)

想要獲取到當前 PreviewView 所使用的縮放 (scale) 類型,調用 PreviewView.getScaleType()) 便可。

PreviewView - 攝像頭控制操做

根據相機攝像頭傳感器的方向、設備的旋轉方向、以及顯示模式和預覽比例,PreviewView 可能會對從相機接收到的預覽幀進行相應地縮放、旋轉和轉換處理,以便在 UI 界面中能正確展現。這也是爲何將 UI 座標轉換成攝像頭傳感器座標是很重要的。在 CameraX 中,這種轉換是由 MeteringPointFactory 完成的,它能夠經過 PreviewView 提供的 API 進行建立: PreviewView.createMeteringPointFactory(cameraSelector)),其中 CameraSelector 參數表明所傳入畫面流數據的攝像頭。

當您須要實現輕點對焦 (tap-to-focus) 功能的時候,PreviewView 的 MeteringPointFactor 輕易就可作到。儘管相機預覽中默認啓用了自動對焦 (須要攝像頭支持),但在 PreviewView 上點擊時,您仍是能夠控制對焦目標。MeteringPointFactory 會將對焦目標的座標轉換爲攝像頭傳感器的座標,而後再使用攝像頭對該區域進行對焦。

下面的示例展現瞭如何使用 觸摸監聽器 (touch listener) 在 PreviewView 上實現輕點對焦功能:

fun onTouch(x: Float, y: Float) {
   // 建立 MeteringPoint,命名爲 factory
    val factory = previewView.createMeteringPointFactory(cameraSelector)

   // 將 UI 界面的座標轉換爲攝像頭傳感器的座標

    val point = factory.createPoint(x, y)
    // 建立對焦須要用的 action 
    val action = FocusMeteringAction.Builder(point).build()
    // 執行所建立的對焦 action
    cameraControl.startFocusAndMetering(action)
}

另外一個在相機預覽界面中經常使用的功能是捏拉縮放 (pinch-to-zoom),它可讓您經過在預覽界面進行捏拉來實現畫面的縮放操做。想要在 PreviewView 上實現它,在其之上添加一個 觸摸監聽器,並將其綁定到縮放手勢監聽器 (scale gesture listener) 上。這樣就能夠作到攔截捏拉手勢,而後相應地更新攝像頭的縮放比例。

下方的示例展現瞭如何在 PreviewView 上實現捏拉縮放 (pinch-to-zoom) 操做:

// 建立一個名爲 listener 的回調函數,當手勢事件發生時會調用這個回調函數

val listener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {

    override fun onScale(detector: ScaleGestureDetector): Boolean {

       // 獲取當前的攝像頭的縮放比例

        val currentZoomRatio: Float = cameraInfo.zoomRatio.value ?: 1F

        // 獲取用戶捏拉手勢所更改的縮放比例

        val delta = detector.scaleFactor

        // 更新攝像頭的縮放比例
        cameraControl.setZoomRatio(currentZoomRatio * delta)
        return true
    }
}

// 將 PreviewView 的觸摸監聽器綁定到縮放手勢監聽器上

val scaleGestureDetector = ScaleGestureDetector(context, listener)

// 將 PreviewView 的觸摸事件傳遞給縮放手勢監聽器上

previewView.setOnTouchListener { _, event ->

    scaleGestureDetector.onTouchEvent(event)
    return@setOnTouchListener true
}

PreviewView - 如何進行測試

PreviewView 可在各類不一樣的 Android 設備上提供一致的相機處理行爲,這要歸功於 CameraX 在 自動化測試實驗室 中對 PreviewView 及其其餘 API 上進行的投資。這些測試主要分爲兩個主要類別:

  • 單元測試 能夠結合當前的實現模式,縮放類型和 MeteringPointFactor 來驗證 PreviewView 的行爲。當出現父級視圖的大小更改,或是展現的佈局發生了變化,亦或是被綁定到 Window 上的狀況時,單元測試還能夠確保 PreviewView 在適當的時候可以正確地去調整預覽畫面;
  • 集成測試 能夠確保 PreviewView 集成到應用中,能夠正常去顯示或者中止顯示來自相機的畫面數據流。這些測試會驗證 preview 在各類狀況時的狀態,包括在應用運行時進行屢次關閉而後從新打開,切換前置後置攝像頭,以及應用的生命週期銷燬後從新建立的狀況。當前這些測試覆蓋的主要範圍是使用 TextureView 做爲 PreviewView 的實現模式,由於使用 SurfaceView 以後想要捕獲相機預覽開始和結束時的信號會很是困難。

總結

綜上所述:

  • PreviewView 是一個自定義的 View,它能夠方便地展現相機的預覽畫面;
  • PreviewView 默認使用 SurfaceView 做爲它預覽畫面 (preview surface) 的實現,可是在須要的時候會轉而使用 TextureView;
  • 將諸如 ImageCaptureImageAnalysis 這樣的用例綁定到 LifecycleOwner 上,建立一個 surfaceProvider,將其綁定到 Preview 用例來啓動相機預覽;
  • 經過定義 PreviewView 的縮放類型來控制預覽畫面的展現方式;
  • 經過給 PreviewView 建立 MeteringPointFactory 來實現對焦功能;
  • 經過給 PreviewView 設置手勢監聽來實現捏拉縮放功能。

想了解更多關於 CameraX 的優秀功能嗎?請查閱如下資料:

相關文章
相關標籤/搜索