1. CameraX预览适配问题全景

1.1 问题背景与适配原理

Android 设备屏幕比例从 16:9、18:9、19.5:9 到 21:9 甚至折叠屏不规则比例,而 Camera 传感器输出比例通常为 4:3 或 16:9。当 PreviewView 或自定义 Surface 的尺寸与相机输出的比例不一致时,系统必须对预览画面进行缩放、裁剪或旋转,产生以下五类核心适配问题:

问题类别表现根本原因
屏幕比例差异预览画面左右/上下出现黑边或白边PreviewView 宽高比 ≠ 相机输出宽高比
裁剪模式不适画面被过度裁剪,丢失重要内容setScaleType 选择不当或未配置
旋转方向异常预览颠倒、横竖屏翻转设备自然方向与传感器方向未对齐
Surface 尺寸不匹配预览模糊、锯齿或黑边Surface 分辨率与相机分析/预览分辨率不匹配
预览拉伸变形人物或物体被横向/纵向拉长View 设置宽度/高度与相机输出比例强制不同
适配核心原则: 保持相机输出的宽高比与 PreviewView 的宽高比一致,再通过合适的裁剪模式处理屏幕显示区域的差异。

1.2 适配实现流程

  1. 获取相机输出尺寸:使用 CameraSelector 和 ProcessCameraProvider 获取支持的预览尺寸列表,选择最接近屏幕比例的尺寸。
  2. 配置 PreviewView 比例:根据选定的相机输出尺寸动态设置 PreviewView 的宽高比(通过 setScaleType 或自定义 View 布局)。
  3. 处理旋转:在 Preview.Builder 中设置 setTargetRotation(),同时监听 Activity 旋转事件,动态更新。
  4. 绑定生命周期:使用 cameraProvider.bindToLifecycle() 完成绑定。
  5. 调试验证:在不同比例设备上观察预览是否变形、黑边或旋转错误。

1.3 关键 API 与配置示例

以下代码展示如何获取最佳预览尺寸并设置 PreviewView 比例:

val metrics = DisplayMetrics().also { windowManager.defaultDisplay.getMetrics(it) }
val screenWidth = metrics.widthPixels
val screenHeight = metrics.heightPixels
val screenAspectRatio = Rational(screenWidth.toLong(), screenHeight.toLong())

val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
    val cameraProvider = cameraProviderFuture.get()

    val preview = Preview.Builder()
        .setTargetAspectRatio(screenAspectRatio)
        .setTargetRotation(windowManager.defaultDisplay.rotation)
        .build()

    val previewView = findViewById<PreviewView>(R.id.preview_view)
    val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

    try {
        cameraProvider.unbindAll()
        val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
        preview.setSurfaceProvider(previewView.surfaceProvider)
    } catch (e: Exception) { e.printStackTrace() }
}, ContextCompat.getMainExecutor(this))
注意: setTargetAspectRatio 接收一个 Rational 对象,并非所有设备都支持任意比例;若无法匹配,CameraX 会自动回退到最接近的支持比例。

1.4 调试方法与验证

在调试阶段,可通过以下方式定位适配问题:

  • 使用 adb shell dumpsys media.camera 查看设备支持的预览尺寸列表。
  • 在 Preview.SurfaceProvider 的回调中打印 Surface 实际尺寸与预期尺寸对比。
  • 在 PreviewView 上叠加半透明网格或参考线布局,肉眼观察变形程度。
  • 利用 CameraX TestLab 库(androidx.camera:camera-testing)编写自动化 UI 测试。

1.5 常见部署陷阱

陷阱错误表现解决方案
未考虑状态栏/导航栏高度预览底部被裁切在 onResume 中重新计算可用显示区域,并调用 previewView.requestLayout()
在 Activity 旋转时未重新计算比例横屏预览被拉伸监听 ConfigurationChanged 或使用 setOnDisplayedMismatchListener
使用固定宽高比而不是动态匹配部分折叠屏设备黑边严重始终根据当前 Display 尺寸动态计算 Rational

1.6 性能与稳定性优化建议

  • 避免频繁重新绑定:仅在比例或旋转确实变化时(如屏幕方向变化)才调用 unbindAll 和 bindToLifecycle。
  • 使用 setScaleType(PreviewView.ScaleType.FILL_CENTER):该模式在保证比例不变的前提下尽可能填满视图,黑边最少。
  • 启用 TextureView 而非 SurfaceView 时注意硬件加速开销,低端设备建议使用 SurfaceView。
  • 缓存最优尺寸:第一次计算后缓存屏幕比例对应的最优相机输出尺寸,避免每次绑定都重新计算。
常见错误点: 不要在 Preview.Builder 中直接调用 setTargetResolution() 时传入与屏幕方向无关的固定尺寸,否则旋转后预览会变形。应始终配合 setTargetRotation() 使用。

1.7 适配全景流程 SVG 图

CameraX 预览适配问题全景流程图 CameraX Preview 屏幕比例差异 裁剪模式不适 旋转方向异常 Surface尺寸不匹配 预览拉伸变形 黑边 / 白边 FILL_CENTER/FIT等 适配策略:动态比例 + 正确裁剪 + 旋转对齐 💡 说明: 五个核心问题彼此关联,通常一个适配方案可解决多个问题(例如动态比例法能同时减少黑边和拉伸)。

1.8 本章小结