使用 PreviewView 来展示相机预览
PreviewView 的介绍
PreviewView 是一个可以显示相机画面的自定义 View,它被构建的初衷便是降低开发者们在设置和处理相机所使用的预览画面 (preview surface) 的难度。
如果您需要在应用中提供展示相机画面的基本功能,使用 PreviewView 是最推荐的做法,它有以下几个优点:
使用简单: PreviewView 是一个 View,它通过管理 Preview 用例所使用的 Surface 来实现将相机捕捉到的画面展示在界面布局中的全部功能; 代码轻量: PreviewView 只专注于实现相机画面预览功能。它所有内部资源都致力于对相机预览画面的展示,以及在相机使用过程中对预览画面 (preview surface) 进行管理。这样的关注点分离使得 PreviewView 的代码能够保持简洁; 支持全面: PreviewView 解决了在屏幕上展示相机画面过程中最难处理的部分,包括对画面宽高比、缩放和旋转的处理。不同的设备会导致不一致的行为,包括设备、屏幕尺寸、摄像头硬件支持水平,还会需要适配诸如分屏模式、不同锁定方向和可动态调节尺寸的展示窗口等显示模式,为了解决这些问题并在多种设备上提供无缝体验,PreviewView 还做了一些兼容性的处理。
Preview
https://developer.android.google.cn/reference/kotlin/androidx/camera/core/Preview.html
PreviewView 的实现模式
PreviewView 是 FrameLayout 的子类,它会使用 SurfaceView 或者 TextureView 展示来自相机捕捉到的画面。一旦相机准备好,就会创建一个预览画面 (preview surface) 的实例,并在相机使用过程中尽量持有该实例,如果相机还在工作中却提前释放了所持有的预览画面 (preview surface) 实例,就会重新创建一个。
SurfaceView https://developer.android.google.cn/reference/android/view/SurfaceView TextureView https://developer.android.google.cn/reference/android/view/TextureView 旧版设备 https://source.android.com/devices/camera/versioning#camera_api2
// 进行相机画面预览之前,设置想要的实现模式
previewView.preferredImplementationMode = ImplementationMode.SURFACE_VIEW
// 将 previewView 设置到 preview 用例中来开始进行相机画面预览
preview.setSurfaceProvider(previewView.createSurfaceProvider(cameraInfo))
PreviewView.setPreferredImplementationMode(ImplementationMode) https://developer.android.google.cn/reference/androidx/camera/view/PreviewView#setPreferredImplementationMode(androidx.camera.view.PreviewView.ImplementationMode) Preview.setSurfaceProvider(PreviewView.createSurfaceProvider()) https://developer.android.google.cn/reference/androidx/camera/core/Preview#setSurfaceProvider(java.util.concurrent.Executor,%20androidx.camera.core.Preview.SurfaceProvider)
PreviewView - Preview
SurfaceProvider
https://developer.android.google.cn/reference/androidx/camera/core/Preview.SurfaceProvider
PreviewView.createSurfaceProvider(CameraInfo)
https://developer.android.google.cn/reference/androidx/camera/view/PreviewView#createSurfaceProvider(androidx.camera.core.CameraInfo)
CameraInfo
https://developer.android.google.cn/reference/androidx/camera/core/CameraInfo
实例
https://developer.android.google.cn/training/camerax#ease-of-use LifecycleOwner https://developer.android.google.cn/reference/androidx/lifecycle/LifecycleOwner
// 创建 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) 类型
how 决定将预览画面放置于 (FIT) 父级视图中还是填充于 (FILL) 父级视图中; where 决定预览画面相对于父级视图来说,是左上方对齐 (START),居中对齐 (CENTER) 还是右下方对齐 (END)。
有两种方法可以设置缩放 (scale) 类型:
通过在 XML 布局文件中设置 PreviewView 的 scaleType 属性来实现,如以下示例所示:
<androidx.camera.view.PreviewView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:scaleType="fitEnd" />
在代码中通过调用 PreviewView.setScaleType(ScaleType) 来实现,如以下示例所示:
previewView.setScaleType(ScaleType.FIT_CENTER)
scaleType https://developer.android.google.cn/reference/androidx/camera/view/PreviewView.ScaleType PreviewView.setScaleType(ScaleType) https://developer.android.google.cn/reference/androidx/camera/view/PreviewView#setScaleType(androidx.camera.view.PreviewView.ScaleType) PreviewView.getScaleType() https://developer.android.google.cn/reference/androidx/camera/view/PreviewView#getScaleType()
PreviewView - 摄像头控制操作
根据相机摄像头传感器的方向、设备的旋转方向、以及显示模式和预览比例,PreviewView 可能会对从相机接收到的预览帧进行相应地缩放、旋转和转换处理,以便在 UI 界面中能正确展示。这也是为什么将 UI 坐标转换成摄像头传感器坐标是很重要的。在 CameraX 中,这种转换是由 MeteringPointFactory 完成的,它可以通过 PreviewView 提供的 API 进行创建: PreviewView.createMeteringPointFactory(cameraSelector),其中 CameraSelector 参数代表所传入画面流数据的摄像头。
MeteringPointFactory https://developer.android.google.cn/reference/androidx/camera/core/MeteringPointFactory PreviewView.createMeteringPointFactory(cameraSelector) https://developer.android.google.cn/reference/androidx/camera/view/PreviewView#createMeteringPointFactory(androidx.camera.core.CameraSelector) CameraSelector https://developer.android.google.cn/reference/kotlin/androidx/camera/core/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)
}
touch listener
https://developer.android.google.cn/reference/android/view/View.OnTouchListener
触摸监听器
https://developer.android.google.cn/reference/android/view/View.OnTouchListenerscale gesture listener https://developer.android.google.cn/reference/android/view/ScaleGestureDetector.SimpleOnScaleGestureListener
下方的示例展示了如何在 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 - 如何进行测试
单元测试可以结合当前的实现模式,缩放类型和 MeteringPointFactor 来验证 PreviewView 的行为。当出现父级视图的大小更改,或是展示的布局发生了变化,亦或是被绑定到 Window 上的情况时,单元测试还可以确保 PreviewView 在适当的时候能够正确地去调整预览画面; 集成测试可以确保 PreviewView 集成到应用中,可以正常去显示或者停止显示来自相机的画面数据流。这些测试会验证 preview 在各种情况时的状态,包括在应用运行时进行多次关闭然后重新打开,切换前置后置摄像头,以及应用的生命周期销毁后重新创建的情况。当前这些测试覆盖的主要范围是使用 TextureView 作为 PreviewView 的实现模式,因为使用 SurfaceView 之后想要捕获相机预览开始和结束时的信号会非常困难。
自动化测试实验室 https://developer.android.google.cn/training/camerax/devices Window https://developer.android.google.cn/reference/android/view/Window
总结
PreviewView 是一个自定义的 View,它可以方便地展示相机的预览画面;
PreviewView 默认使用 SurfaceView 作为它预览画面 (preview surface) 的实现,但是在需要的时候会转而使用 TextureView;
将诸如 ImageCapture 和 ImageAnalysis 这样的用例绑定到 LifecycleOwner 上,创建一个 surfaceProvider,将其绑定到 Preview 用例来启动相机预览;
通过定义 PreviewView 的缩放类型来控制预览画面的展示方式;
通过给 PreviewView 创建 MeteringPointFactory 来实现对焦功能;
通过给 PreviewView 设置手势监听来实现捏拉缩放功能。
Android 开发文档 | CameraX 概览
https://developer.android.google.cn/training/camerax Codelab | CameraX
Codelab | CameraX 使用指南
https://codelabs.developers.google.com/codelabs/camerax-getting-started
社区 | CameraX 线上开发者社区
https://groups.google.com/a/android.com/g/camerax-developers
示例代码 | 使用 CameraX 构建相机应用
https://github.com/android/camera-samples/tree/master/CameraXBasic
如果您有 PreviewView 或 Preview 相关的问题,欢迎在下方评论区留言。感谢您的阅读!
推荐阅读