ViewPort 配置实战

ViewPort 是 CameraX 预览适配的核心配置单元,它同时包含了目标尺寸、宽高比、缩放类型和旋转角度四个关键维度。通过 PreviewView.setViewPort() 方法,开发者可以将这些维度一次性传递给 CameraX 的预览管线,从而让 CameraX 自动完成输入(传感器)与输出(显示)之间的边界适配。

4.1 setViewPort() 方法签名与参数

在 Jetpack CameraX 1.0+ 中,setViewPort() 的完整签名如下:

public void setViewPort(@Nullable ViewPort viewPort)

而 ViewPort 对象的构建依赖 ViewPort.Builder,其关键参数包括:

参数 类型 说明
aspectRatio Rational 预览输出的宽高比,例如 new Rational(4, 3) 或 new Rational(16, 9)
scaleType int 预览裁剪/填充策略,取值 FILL_CENTER / FILL_START / FILL_END / FIT_CENTER
rotation int 预览相对于设备自然方向的旋转角度:0、90、180、270
layoutDirection int 布局方向,通常保持默认 View.LAYOUT_DIRECTION_LTR

注意:Builder 没有 width / height 直接参数;CameraX 会根据 aspectRatio 和 scaleType 自动计算最终的裁剪区域,而实际显示尺寸由 PreviewView 的布局宽高决定。

4.2 宽高比设置:匹配屏幕还是传感器?

宽高比的设定直接影响预览画面是否变形。常见策略有两种:

  • 匹配屏幕宽高比:Rational(displayWidth, displayHeight) —— 预览输出会被裁剪到与屏幕完全一致,画面无黑边但可能牺牲传感器部分视场。
  • 匹配传感器宽高比:从支持的 Size 列表中选取(例如 4:3 或 16:9),然后允许 PreviewView 通过 scaleType 进行缩放填充。

🔧 最佳实践:若追求最大视场且允许轻微裁剪,使用传感器原生比 + FILL_CENTER;若要求无裁剪全显示,使用屏幕比 + FIT_CENTER(会产生黑边)。

示例:设置 16:9 宽高比:

// 假设屏幕分辨率 1080×1920,转化为 Rational
Rational displayAspect = new Rational(1080, 1920);
// 或直接指定传感器支持的常见比
Rational sensorAspect = new Rational(16, 9);

4.3 ScaleType 选择与效果

CameraX 的 PreviewView 提供四种 scaleType,它们决定了预览画面如何适配 PreviewView 的显示区域:

ScaleType 行为 典型场景
FILL_CENTER 等比缩放至充满控件,超出部分裁剪,画面居中 全屏预览,无黑边
FILL_START 等比缩放至充满控件,裁剪后左/上对齐 取景器左对齐布局
FILL_END 等比缩放至充满控件,裁剪后右/下对齐 取景器右对齐布局
FIT_CENTER 等比缩放至完全显示,保留黑边或空白 需要完整画面、允许黑边

⚠️ 注意:FIT_CENTER 虽然能显示完整画面,但会在 PreviewView 边界内产生未使用区域(黑边或透明)。若将这些区域设为透明,需确保底层布局背景色与 UI 设计一致。

代码设置示例:

ViewPort viewPort = new ViewPort.Builder(
        Rational.parseRational("16:9"),   // aspectRatio
        PreviewView.FILL_CENTER           // scaleType
    )
    .setRotation(Surface.ROTATION_0)
    .build();
previewView.setViewPort(viewPort);

4.4 旋转角度传递

摄像头传感器的物理方向通常与设备自然方向不同(例如手机前置摄像头默认旋转 270°)。旋转角度的传递保证预览画面与 UI 方向一致。

  • 来源:通过 WindowManager.getDefaultDisplay().getRotation() 获取当前显示旋转。
  • 传递方式:构建 ViewPort 时传入 setRotation(displayRotation),CameraX 会结合摄像头朝向信息自动校准输出。
int rotation = previewView.getDisplay().getRotation();
ViewPort viewPort = new ViewPort.Builder(aspect, scaleType)
    .setRotation(rotation)
    .build();
previewView.setViewPort(viewPort);

💡 经验:旋转角度必须在每次 onDisplayChanged 回调中重新设置,否则屏幕旋转后预览可能出现方向错误。建议将 setViewPort() 封装成方法,在 DisplayManager.DisplayListener 中调用。

4.5 综合流程图:ViewPort 如何影响预览适配

ViewPort 配置与预览适配流程 PreviewView 布局宽高(px) ViewPort.Builder aspectRatio + scaleType + rotation Preview use case 输出尺寸 & 裁剪 CameraX 内部计算 选择最接近的传感器分辨率 → 应用裁剪 & 旋转 最终预览画面 无变形,方向正确 setViewPort() 应在 PreviewView 显示前调用,且每次配置变化后重新调用

4.6 常见错误与调试

  1. 宽高比设置错误导致画面拉伸:Rational 分子分母顺序颠倒。例如 new Rational(1920, 1080) 代表 1920:1080,若屏幕实际是 1080×1920,应使用 new Rational(1080, 1920) 或 Rational.parseRational("9:16")。
  2. 未传递旋转角度:在非自然方向下预览出现 90° 翻转。解决:通过 display.getRotation() 动态获取并设置。
  3. ScaleType 误用为 FIT_CENTER 导致取景器变小:检查 PreviewView 的 layout_width/height 是否为 match_parent,确保显示区域足够大。
  4. 多次设置 setViewPort() 导致内部状态不一致:建议在配置变更时先调用 previewView.setViewPort(null) 重置,再设置新值。

4.7 性能与稳定性优化

  • 缓存宽高比对象:Rational 创建开销可忽略,但避免在热点循环(如 onDraw)中重复构造。
  • 延迟设置旋转角度:在 PreviewView 的 onDisplayChanged 回调中执行,减少频繁调用。
  • 避免混用 setViewPort() 与直接修改 PreviewView 的 scaleX/scaleY:两者作用域重叠可能导致缩放计算冲突,推荐只通过 ViewPort 控制。
  • 测试覆盖:在不同宽高比设备(平板、折叠屏)上验证 FILL_CENTER 和 FIT_CENTER 行为,确保 UI 边距无异常。