使用 PreviewView 來(lái)展示相機(jī)預(yù)覽

顯示相機(jī)預(yù)覽內(nèi)容是每個(gè)相機(jī)類應(yīng)用都會(huì)包含的功能蝶棋,想要完美實(shí)現(xiàn)這個(gè)卻并非易事坷衍。原因是疙渣,在某些特別極端情況下 camera2 API 的使用會(huì)變得很復(fù)雜且轨,而且在不同設(shè)備上的行為還會(huì)有所不同酒甸。還好魄健,Jetpack CameraX 庫(kù)的 PreviewView 可以幫助您解決這一問(wèn)題。通過(guò)在各種 Android 設(shè)備上提供開(kāi)發(fā)者友好烘挫、一致且穩(wěn)定的 API诀艰,使得展示相機(jī)的預(yù)覽變得不再困難。

PreviewView 的介紹

PreviewView 是一個(gè)可以顯示相機(jī)畫面的自定義 View饮六,它被構(gòu)建的初衷便是降低開(kāi)發(fā)者們?cè)谠O(shè)置和處理相機(jī)所使用的預(yù)覽畫面 (preview surface) 的難度其垄。

如果您需要在應(yīng)用中提供展示相機(jī)畫面的基本功能,使用 PreviewView 是最推薦的做法卤橄,它有以下幾個(gè)優(yōu)點(diǎn):

  • 使用簡(jiǎn)單: PreviewView 是一個(gè) View绿满,它通過(guò)管理 Preview 用例所使用的 Surface 來(lái)實(shí)現(xiàn)將相機(jī)捕捉到的畫面展示在界面布局中的全部功能;
  • 代碼輕量: PreviewView 只專注于實(shí)現(xiàn)相機(jī)畫面預(yù)覽功能窟扑。它所有內(nèi)部資源都致力于對(duì)相機(jī)預(yù)覽畫面的展示喇颁,以及在相機(jī)使用過(guò)程中對(duì)預(yù)覽畫面 (preview surface) 進(jìn)行管理。這樣的關(guān)注點(diǎn)分離使得 PreviewView 的代碼能夠保持簡(jiǎn)潔嚎货;
  • 支持全面: PreviewView 解決了在屏幕上展示相機(jī)畫面過(guò)程中最難處理的部分橘霎,包括對(duì)畫面寬高比、縮放和旋轉(zhuǎn)的處理殖属。不同的設(shè)備會(huì)導(dǎo)致不一致的行為姐叁,包括設(shè)備、屏幕尺寸洗显、攝像頭硬件支持水平外潜,還會(huì)需要適配諸如分屏模式、不同鎖定方向和可動(dòng)態(tài)調(diào)節(jié)尺寸的展示窗口等顯示模式挠唆,為了解決這些問(wèn)題并在多種設(shè)備上提供無(wú)縫體驗(yàn)处窥,PreviewView 還做了一些兼容性的處理。

PreviewView 的實(shí)現(xiàn)模式

PreviewView 是 FrameLayout 的子類玄组,它會(huì)使用 SurfaceView 或者 TextureView 展示來(lái)自相機(jī)捕捉到的畫面滔驾。一旦相機(jī)準(zhǔn)備好谒麦,就會(huì)創(chuàng)建一個(gè)預(yù)覽畫面 (preview surface) 的實(shí)例,并在相機(jī)使用過(guò)程中盡量持有該實(shí)例嵌灰,如果相機(jī)還在工作中卻提前釋放了所持有的預(yù)覽畫面 (preview surface) 實(shí)例弄匕,就會(huì)重新創(chuàng)建一個(gè)颅悉。

當(dāng)涉及到諸如功耗和響應(yīng)時(shí)間這些關(guān)鍵指標(biāo)時(shí)沽瞭,SurfaceView 的表現(xiàn)一般都比 TextureView 要好,這也是為什么 PreviewView 會(huì)將 SurfaceView 作為默認(rèn)實(shí)現(xiàn)模式的原因剩瓶。然而驹溃,一些設(shè)備 (主要是一些舊版設(shè)備) 會(huì)在預(yù)覽畫面 (preview surface) 過(guò)早釋放時(shí)出現(xiàn)閃退的情況⊙邮铮可惜的是豌鹤,使用 SurfaceView 時(shí)無(wú)法控制何時(shí)對(duì)畫面 (surface) 進(jìn)行釋放,因?yàn)檫@是由 View 層級(jí)結(jié)構(gòu)所控制的枝缔。因此在這些設(shè)備上布疙,PreviewView 只能使用 TextureView 作為實(shí)現(xiàn)模式。另外在需要對(duì)相機(jī)預(yù)覽界面進(jìn)行旋轉(zhuǎn)愿卸、改變透明度或加入動(dòng)畫的情況下灵临,您也應(yīng)該強(qiáng)制 PreviewView 使用 TextureView 作為實(shí)現(xiàn)模式。

您可以通過(guò)調(diào)用 PreviewView.setPreferredImplementationMode(ImplementationMode) 并設(shè)置 ImplementationMode 參數(shù)為 SURFACE_VIEW 或 TEXTURE_VIEW 來(lái)更改 PreviewView 的實(shí)現(xiàn)模式趴荸。當(dāng)首選模式設(shè)置為 SURFACE_VIEW 時(shí)儒溉,PreviewView 會(huì)盡可能遵循您的設(shè)置 (使用 SurfaceView);而當(dāng)首選模式設(shè)置為 TEXTURE_VIEW 時(shí)发钝,PreviewView 會(huì)確保一直使用 TEXTURE_VIEW 模式顿涣。

?? 在開(kāi)始使用 PreviewView 之前,請(qǐng)務(wù)必通過(guò)調(diào)用 Preview.setSurfaceProvider(PreviewView.createSurfaceProvider()) 來(lái)設(shè)置您想要的實(shí)現(xiàn)模式酝豪。

下面介紹如何設(shè)置 PreviewView 的實(shí)現(xiàn)模式:


// 進(jìn)行相機(jī)畫面預(yù)覽之前涛碑,設(shè)置想要的實(shí)現(xiàn)模式
previewView.preferredImplementationMode = ImplementationMode.SURFACE_VIEW

// 將 previewView 設(shè)置到 preview 用例中來(lái)開(kāi)始進(jìn)行相機(jī)畫面預(yù)覽
preview.setSurfaceProvider(previewView.createSurfaceProvider(camer

PreviewView - Preview

PreviewView 通過(guò)處理創(chuàng)建 Preview 用例所需要的 SurfaceProvider,來(lái)啟動(dòng)一個(gè)預(yù)覽畫面的數(shù)據(jù)流孵淘。SurfaceProvider 會(huì)準(zhǔn)備好需要提供給相機(jī)的 Surface蒲障,用來(lái)對(duì)預(yù)覽畫面的數(shù)據(jù)流進(jìn)行展示,并負(fù)責(zé)在必要時(shí)重新創(chuàng)建 Surface夺英。PreviewView.createSurfaceProvider(CameraInfo) 接收一個(gè) nullable 的 CameraInfo 實(shí)例晌涕。PreviewView 會(huì)結(jié)合所傳入的 CameraInfo 參數(shù),以及您所設(shè)定的實(shí)現(xiàn)模式和當(dāng)前相機(jī)具備功能痛悯,來(lái)決定內(nèi)部如何進(jìn)行功能上的實(shí)現(xiàn)余黎。如果您所傳入的 CameraInfo 是一個(gè) null,那 PreviewView 會(huì)使用 TextureView 作為實(shí)現(xiàn)模式载萌,因?yàn)樗鼰o(wú)法確定所選的相機(jī)若使用 SurfaceView 是否可以正常工作惧财。

一旦您創(chuàng)建好了 Preview 用例和一些別的所需要的實(shí)例后巡扇,將它們綁定至 LifecycleOwner,使用所綁定的相機(jī)的 CameraInfo 來(lái)創(chuàng)建 SurfaceProvider垮衷,再將其綁定至 Preview 用例厅翔,調(diào)用 Preview.setSurfaceProvider(SurfaceProvider) 來(lái)啟動(dòng)預(yù)覽畫面數(shù)據(jù)流。

下面的例子展示了如何將 PreviewView 綁定至 Preview 來(lái)開(kāi)啟預(yù)覽畫面數(shù)據(jù)流:


// 創(chuàng)建 preview 用例
val preview = Preview.Builder().build()

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

// 使用所綁定相機(jī)的 cameraInfo 創(chuàng)建 surfaceProvider
val surfaceProvider = previewView.createSurfaceProvider(camera.cameraInfo)

// 將 surfaceProvider 綁定至 preview 用例來(lái)啟動(dòng)預(yù)覽
preview.setSurfaceProvider(surfaceProvider)

PreviewView - 縮放 (scale) 類型

PreviewView 提供了一個(gè) API搀突,通過(guò)它可以讓您控制預(yù)覽畫面的樣式是怎樣的
(how) 和在父級(jí)視圖中的位置 (where):

  • how決定將預(yù)覽畫面放置于 (FIT) 父級(jí)視圖中還是填充于 (FILL) 父級(jí)視圖中刀闷;
  • where 決定預(yù)覽畫面相對(duì)于父級(jí)視圖來(lái)說(shuō),是左上方對(duì)齊 (START)仰迁,居中對(duì)齊 (CENTER) 還是右下方對(duì)齊 (END)甸昏。

"how" 和 "where" 所組合出來(lái)的結(jié)果,代表了 PreviewView 支持的縮放 (scale) 類型徐许,包括 FIT_START施蜜、FIT_CENTER、FIT_END雌隅、FILL_START翻默、FILL_CENTER and FILL_END。其中最常用的是 FIT_CENTER 和 FILL_CENTER恰起,前者將預(yù)覽界面在保證寬高比的前提下進(jìn)行縮放然后居中修械,后者不會(huì)進(jìn)行縮放,保證居中但是可能會(huì)導(dǎo)致畫面被裁剪村缸。

有兩種方法可以設(shè)置縮放 (scale) 類型:

  • 通過(guò)在 XML 布局文件中設(shè)置 PreviewView 的 scaleType 屬性來(lái)實(shí)現(xiàn)祠肥,如以下示例所示:
<androidx.camera.view.PreviewView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:scaleType="fitEnd" />
previewView.setScaleType(ScaleType.FIT_CENTER)

想要獲取到當(dāng)前 PreviewView 所使用的縮放 (scale) 類型梯皿,調(diào)用 PreviewView.getScaleType() 即可仇箱。

PreviewView - 攝像頭控制操作

根據(jù)相機(jī)攝像頭傳感器的方向、設(shè)備的旋轉(zhuǎn)方向东羹、以及顯示模式和預(yù)覽比例剂桥,PreviewView 可能會(huì)對(duì)從相機(jī)接收到的預(yù)覽幀進(jìn)行相應(yīng)地縮放、旋轉(zhuǎn)和轉(zhuǎn)換處理属提,以便在 UI 界面中能正確展示权逗。這也是為什么將 UI 坐標(biāo)轉(zhuǎn)換成攝像頭傳感器坐標(biāo)是很重要的。在 CameraX 中冤议,這種轉(zhuǎn)換是由 MeteringPointFactory 完成的斟薇,它可以通過(guò) PreviewView 提供的 API 進(jìn)行創(chuàng)建: PreviewView.createMeteringPointFactory(cameraSelector),其中 CameraSelector 參數(shù)代表所傳入畫面流數(shù)據(jù)的攝像頭恕酸。

當(dāng)您需要實(shí)現(xiàn)輕點(diǎn)對(duì)焦 (tap-to-focus) 功能的時(shí)候堪滨,PreviewView 的 MeteringPointFactor 輕易就可做到。盡管相機(jī)預(yù)覽中默認(rèn)啟用了自動(dòng)對(duì)焦 (需要攝像頭支持)蕊温,但在 PreviewView 上點(diǎn)擊時(shí)袱箱,您還是可以控制對(duì)焦目標(biāo)遏乔。MeteringPointFactory 會(huì)將對(duì)焦目標(biāo)的坐標(biāo)轉(zhuǎn)換為攝像頭傳感器的坐標(biāo),然后再使用攝像頭對(duì)該區(qū)域進(jìn)行對(duì)焦发笔。

下面的示例展示了如何使用觸摸監(jiān)聽(tīng)器 (touch listener) 在 PreviewView 上實(shí)現(xiàn)輕點(diǎn)對(duì)焦功能:


 fun onTouch(x: Float, y: Float) {
   // 創(chuàng)建 MeteringPoint盟萨,命名為 factory
    val factory = previewView.createMeteringPointFactory(cameraSelector)
  
   // 將 UI 界面的坐標(biāo)轉(zhuǎn)換為攝像頭傳感器的坐標(biāo)
    val point = factory.createPoint(x, y)
  
    // 創(chuàng)建對(duì)焦需要用的 action 
    val action = FocusMeteringAction.Builder(point).build()
  
    // 執(zhí)行所創(chuàng)建的對(duì)焦 action
    cameraControl.startFocusAndMetering(action)
}

另一個(gè)在相機(jī)預(yù)覽界面中常用的功能是捏拉縮放 (pinch-to-zoom),它可以讓您通過(guò)在預(yù)覽界面進(jìn)行捏拉來(lái)實(shí)現(xiàn)畫面的縮放操作了讨。想要在 PreviewView 上實(shí)現(xiàn)它捻激,在其之上添加一個(gè)觸摸監(jiān)聽(tīng)器,并將其綁定到縮放手勢(shì)監(jiān)聽(tīng)器 (scale gesture listener) 上量蕊。這樣就可以做到攔截捏拉手勢(shì)铺罢,然后相應(yīng)地更新攝像頭的縮放比例艇挨。

下方的示例展示了如何在 PreviewView 上實(shí)現(xiàn)捏拉縮放 (pinch-to-zoom) 操作:


// 創(chuàng)建一個(gè)名為 listener 的回調(diào)函數(shù)残炮,當(dāng)手勢(shì)事件發(fā)生時(shí)會(huì)調(diào)用這個(gè)回調(diào)函數(shù)
val listener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
    override fun onScale(detector: ScaleGestureDetector): Boolean {
       // 獲取當(dāng)前的攝像頭的縮放比例
        val currentZoomRatio: Float = cameraInfo.zoomRatio.value ?: 1F
      
        // 獲取用戶捏拉手勢(shì)所更改的縮放比例
        val delta = detector.scaleFactor
      
        // 更新攝像頭的縮放比例
        cameraControl.setZoomRatio(currentZoomRatio * delta)
        return true
    }
}
// 將 PreviewView 的觸摸監(jiān)聽(tīng)器綁定到縮放手勢(shì)監(jiān)聽(tīng)器上
val scaleGestureDetector = ScaleGestureDetector(context, listener)

// 將 PreviewView 的觸摸事件傳遞給縮放手勢(shì)監(jiān)聽(tīng)器上
previewView.setOnTouchListener { _, event ->
    scaleGestureDetector.onTouchEvent(event)
    return@setOnTouchListener true
}

PreviewView - 如何進(jìn)行測(cè)試

PreviewView 可在各種不同的 Android 設(shè)備上提供一致的相機(jī)處理行為,這要?dú)w功于 CameraX 在自動(dòng)化測(cè)試實(shí)驗(yàn)室中對(duì) PreviewView 及其其他 API 上進(jìn)行的投資缩滨。這些測(cè)試主要分為兩個(gè)主要類別:

  • 單元測(cè)試可以結(jié)合當(dāng)前的實(shí)現(xiàn)模式势就,縮放類型和 MeteringPointFactor 來(lái)驗(yàn)證 PreviewView 的行為。當(dāng)出現(xiàn)父級(jí)視圖的大小更改脉漏,或是展示的布局發(fā)生了變化苞冯,亦或是被綁定到 Window 上的情況時(shí),單元測(cè)試還可以確保 PreviewView 在適當(dāng)?shù)臅r(shí)候能夠正確地去調(diào)整預(yù)覽畫面侧巨;
  • 集成測(cè)試可以確保 PreviewView 集成到應(yīng)用中舅锄,可以正常去顯示或者停止顯示來(lái)自相機(jī)的畫面數(shù)據(jù)流。這些測(cè)試會(huì)驗(yàn)證 preview 在各種情況時(shí)的狀態(tài)司忱,包括在應(yīng)用運(yùn)行時(shí)進(jìn)行多次關(guān)閉然后重新打開(kāi)皇忿,切換前置后置攝像頭,以及應(yīng)用的生命周期銷毀后重新創(chuàng)建的情況坦仍。當(dāng)前這些測(cè)試覆蓋的主要范圍是使用 TextureView 作為 PreviewView 的實(shí)現(xiàn)模式鳍烁,因?yàn)槭褂?SurfaceView 之后想要捕獲相機(jī)預(yù)覽開(kāi)始和結(jié)束時(shí)的信號(hào)會(huì)非常困難。

總結(jié)

綜上所述:

  • PreviewView 是一個(gè)自定義的 View繁扎,它可以方便地展示相機(jī)的預(yù)覽畫面幔荒;
  • PreviewView 默認(rèn)使用 SurfaceView 作為它預(yù)覽畫面 (preview surface) 的實(shí)現(xiàn),但是在需要的時(shí)候會(huì)轉(zhuǎn)而使用 TextureView梳玫;
  • 將諸如 ImageCapture 和 ImageAnalysis 這樣的用例綁定到 LifecycleOwner 上爹梁,創(chuàng)建一個(gè) surfaceProvider,將其綁定到 Preview 用例來(lái)啟動(dòng)相機(jī)預(yù)覽提澎;
  • 通過(guò)定義 PreviewView 的縮放類型來(lái)控制預(yù)覽畫面的展示方式姚垃;
  • 通過(guò)給 PreviewView 創(chuàng)建 MeteringPointFactory 來(lái)實(shí)現(xiàn)對(duì)焦功能;
  • 通過(guò)給 PreviewView 設(shè)置手勢(shì)監(jiān)聽(tīng)來(lái)實(shí)現(xiàn)捏拉縮放功能虱朵。

想了解更多關(guān)于 CameraX 的優(yōu)秀功能嗎莉炉?請(qǐng)查閱以下資料及推薦閱讀:

如果您有 PreviewView 或 Preview 相關(guān)的問(wèn)題钓账,歡迎在下方評(píng)論區(qū)留言。感謝您的閱讀絮宁!

點(diǎn)擊這里了解更多 CameraX 相關(guān)內(nèi)容

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末梆暮,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子绍昂,更是在濱河造成了極大的恐慌啦粹,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窘游,死亡現(xiàn)場(chǎng)離奇詭異唠椭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)忍饰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門贪嫂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人艾蓝,你說(shuō)我怎么就攤上這事力崇。” “怎么了赢织?”我有些...
    開(kāi)封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵亮靴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我于置,道長(zhǎng)茧吊,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任八毯,我火速辦了婚禮搓侄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宪彩。我一直安慰自己休讳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布尿孔。 她就那樣靜靜地躺著俊柔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪活合。 梳的紋絲不亂的頭發(fā)上雏婶,一...
    開(kāi)封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音白指,去河邊找鬼留晚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛告嘲,可吹牛的內(nèi)容都是我干的错维。 我是一名探鬼主播奖地,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼赋焕!你這毒婦竟也來(lái)了参歹?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤隆判,失蹤者是張志新(化名)和其女友劉穎犬庇,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體侨嘀,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡臭挽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了咬腕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欢峰。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖郎汪,靈堂內(nèi)的尸體忽然破棺而出赤赊,到底是詐尸還是另有隱情,我是刑警寧澤煞赢,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站哄孤,受9級(jí)特大地震影響照筑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘦陈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一凝危、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晨逝,春花似錦蛾默、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至趁窃,卻和暖如春牧挣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背醒陆。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工瀑构, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人刨摩。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓寺晌,卻偏偏與公主長(zhǎng)得像世吨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子呻征,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353