4. CameraX 实战:预览画面
本章将引导你完成 CameraX 预览管线的核心配置。你将学习如何将 PreviewView 嵌入布局、绑定生命周期、实现实时预览,并正确处理设备旋转与配置变更,确保预览画面始终正确显示。
4.1 PreviewView 组件与布局集成
PreviewView 是 CameraX 提供的专用视图,用于显示相机预览流。它内部封装了 SurfaceView 和 TextureView 的切换逻辑,并自动处理缩放、裁剪与变换。
4.1.1 添加依赖与布局声明
在模块级 build.gradle 中引入 CameraX 核心库与 camera-view 扩展库:
dependencies {
// CameraX 核心库
def camerax_version = "1.3.1"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
// PreviewView 专用库
implementation "androidx.camera:camera-view:${camerax_version}"
}
在布局文件中声明 PreviewView,建议使用 match_parent 铺满预览区域:
<!-- fragment_camera.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- 其他 UI 控件(扫码框、文档边界等) -->
</androidx.constraintlayout.widget.ConstraintLayout>
4.1.2 PreviewView 缩放模式
PreviewView 提供多种 ScaleType 控制预览画面适配方式,下表列出常用模式:
| ScaleType | 行为描述 | 适用场景 |
|---|---|---|
FILL_CENTER |
保持宽高比缩放,填满视图,超出部分裁剪 | 全屏预览、扫码相机 |
FIT_CENTER |
保持宽高比缩放,完整显示画面,可能有黑边 | 文档扫描、需要看到完整画面 |
CENTER_CROP |
不保持宽高比,直接拉伸填满 | 极少使用,画面会变形 |
// 推荐扫码场景使用 FILL_CENTER
previewView.scaleType = PreviewView.ScaleType.FILL_CENTER
FILL_CENTER 避免黑边;文档扫描建议使用 FIT_CENTER 确保文档边缘不被裁剪。
4.2 实现相机预览
预览的核心流程包括:获取 ProcessCameraProvider、绑定生命周期、创建 Preview 用例并关联 PreviewView。
4.2.1 预览管线架构
下图展示了 CameraX 预览管线的核心组件与数据流向:
4.2.2 核心代码实现
在 Fragment 或 Activity 中实现预览的完整步骤:
class CameraFragment : Fragment() {
private var _binding: FragmentCameraBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentCameraBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
startCamera()
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
// 1. 选择后置摄像头
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
// 2. 创建 Preview 用例
val preview = Preview.Builder()
.build()
// 3. 将 Preview 与 PreviewView 的 SurfaceProvider 关联
preview.setSurfaceProvider(binding.previewView.surfaceProvider)
// 4. 绑定生命周期
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
viewLifecycleOwner,
cameraSelector,
preview
)
} catch (e: Exception) {
Log.e(TAG, "预览绑定失败", e)
}
}, ContextCompat.getMainExecutor(requireContext()))
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
setSurfaceProvider()必须在bindToLifecycle()之前调用,否则预览可能黑屏。- 每次重新绑定前调用
unbindAll()释放旧用例,避免资源泄漏。 - 使用
viewLifecycleOwner而非this,确保 Fragment 视图销毁时自动解绑。
4.3 处理屏幕旋转与配置变更
设备旋转时,CameraX 默认会重新创建 SurfaceProvider 并触发预览重建。如果不做处理,会导致预览画面方向错误或短暂黑屏。
4.3.1 旋转角度与预览方向
CameraX 使用 DisplayOrientedMeteringPointFactory 自动处理旋转,但 PreviewView 的宽高比需要与传感器方向对齐。下表列出常见旋转场景的处理策略:
| 设备方向 | 传感器方向 | Preview 旋转角度 | 预览效果 |
|---|---|---|---|
| 竖屏(Portrait) | 90°(后置) | 自动旋转 90° | 正常竖屏预览 |
| 横屏(Landscape) | 0°(后置) | 无需旋转 | 正常横屏预览 |
| 倒置竖屏 | 270°(后置) | 自动旋转 270° | 正常倒置预览 |
4.3.2 配置变更处理方案
推荐使用 CameraX 内置的自动旋转处理,无需手动监听旋转事件。只需在 AndroidManifest.xml 中声明 configChanges 避免 Activity 重建:
<activity
android:name=".CameraActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:screenOrientation="portrait" />
然后在 Activity 或 Fragment 中重写 onConfigurationChanged,更新 PreviewView 的布局参数:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// 更新 PreviewView 的宽高比
val previewView = binding.previewView
val layoutParams = previewView.layoutParams
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
} else {
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
}
previewView.layoutParams = layoutParams
}
4.3.3 旋转处理流程图
- 不要在
onConfigurationChanged中重新调用startCamera(),否则会导致预览闪烁。 - 不要手动设置
Preview.Builder.setTargetRotation(),除非你需要自定义旋转逻辑。 - 如果使用
SurfaceView模式,旋转后可能出现黑屏,建议使用TextureView模式(默认)。
4.4 本章小结
通过本章学习,你已掌握 CameraX 预览的核心技能:
- PreviewView 集成:布局声明、缩放模式选择、依赖配置。
- 预览实现:
ProcessCameraProvider获取、Preview用例创建、SurfaceProvider绑定、生命周期关联。 - 旋转处理:
configChanges声明、onConfigurationChanged布局更新、CameraX 自动方向校正。