顯示相機(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" />
- 在代碼中通過(guò)調(diào)用 PreviewView.setScaleType(ScaleType) 來(lái)實(shí)現(xiàn),如以下示例所示:
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)查閱以下資料及推薦閱讀:
- Android 開(kāi)發(fā)文檔 | CameraX 概覽
https://developer.android.google.cn/training/camerax Codelab | CameraX - Codelab | CameraX 使用指南
https://codelabs.developers.google.com/codelabs/camerax-getting-started - 社區(qū) | CameraX 線上開(kāi)發(fā)者社區(qū)
https://groups.google.com/a/android.com/g/camerax-developers - 示例代碼 | 使用 CameraX 構(gòu)建相機(jī)應(yīng)用
https://github.com/android/camera-samples/tree/master/CameraXBasic
如果您有 PreviewView 或 Preview 相關(guān)的問(wèn)題钓账,歡迎在下方評(píng)論區(qū)留言。感謝您的閱讀絮宁!
點(diǎn)擊這里了解更多 CameraX 相關(guān)內(nèi)容