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