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 预览管线的核心组件与数据流向:

CameraX 预览管线架构 CameraSelector Preview 用例 SurfaceProvider PreviewView LifecycleOwner ProcessCameraProvider 📌 数据流:CameraSelector → Preview 用例 → SurfaceProvider → PreviewView 生命周期绑定:LifecycleOwner 控制预览启动/停止

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 更新 PreviewView 布局 CameraX 自动旋转预览 ✅ 无需手动旋转 Preview 画面,CameraX 自动处理方向校正
🚨 常见陷阱:
  • 不要在 onConfigurationChanged 中重新调用 startCamera(),否则会导致预览闪烁。
  • 不要手动设置 Preview.Builder.setTargetRotation(),除非你需要自定义旋转逻辑。
  • 如果使用 SurfaceView 模式,旋转后可能出现黑屏,建议使用 TextureView 模式(默认)。

4.4 本章小结

通过本章学习,你已掌握 CameraX 预览的核心技能:

  • PreviewView 集成:布局声明、缩放模式选择、依赖配置。
  • 预览实现:ProcessCameraProvider 获取、Preview 用例创建、SurfaceProvider 绑定、生命周期关联。
  • 旋转处理:configChanges 声明、onConfigurationChanged 布局更新、CameraX 自动方向校正。