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 如何影响预览适配
4.6 常见错误与调试
- 宽高比设置错误导致画面拉伸:
Rational分子分母顺序颠倒。例如new Rational(1920, 1080)代表 1920:1080,若屏幕实际是 1080×1920,应使用new Rational(1080, 1920)或Rational.parseRational("9:16")。 - 未传递旋转角度:在非自然方向下预览出现 90° 翻转。解决:通过
display.getRotation()动态获取并设置。 - ScaleType 误用为
FIT_CENTER导致取景器变小:检查PreviewView的layout_width/height是否为match_parent,确保显示区域足够大。 - 多次设置
setViewPort()导致内部状态不一致:建议在配置变更时先调用previewView.setViewPort(null)重置,再设置新值。
4.7 性能与稳定性优化
- 缓存宽高比对象:
Rational创建开销可忽略,但避免在热点循环(如onDraw)中重复构造。 - 延迟设置旋转角度:在
PreviewView的onDisplayChanged回调中执行,减少频繁调用。 - 避免混用
setViewPort()与直接修改PreviewView的 scaleX/scaleY:两者作用域重叠可能导致缩放计算冲突,推荐只通过ViewPort控制。 - 测试覆盖:在不同宽高比设备(平板、折叠屏)上验证
FILL_CENTER和FIT_CENTER行为,确保 UI 边距无异常。