
本文详解如何在 camera2 api 中正确配置 textureview 的宽高比和旋转角度,解决预览图像拉伸、方向错乱(如横屏偏转±90°、倒置翻转)等常见问题,确保预览画面与实际拍摄视角一致。
本文详解如何在 camera2 api 中正确配置 textureview 的宽高比和旋转角度,解决预览图像拉伸、方向错乱(如横屏偏转±90°、倒置翻转)等常见问题,确保预览画面与实际拍摄视角一致。
在基于 Camera2 API 构建相机预览时,TextureView 因其支持硬件加速和灵活变换被广泛采用。但开发者常遇到两大核心问题:预览画面比例失真(横向拉伸/纵向压缩) 和 设备旋转后预览方向错位(如竖屏正常、横屏逆时针转90°、倒置时镜像翻转)。这些问题并非 Camera2 本身缺陷,而是由相机传感器原始输出方向、设备显示坐标系、TextureView 渲染逻辑三者未对齐所致。以下提供经过验证的完整解决方案。
✅ 正确处理宽高比(避免拉伸)
关键在于:TextureView 的测量逻辑必须严格匹配当前设备朝向下的相机预览尺寸比例,并保持该比例恒定。Camera2 的 PreviewSize(如 1920×1080)是按传感器物理方向定义的(通常为横向),而 UI 布局会随 Activity 方向变化。因此不能直接用 mPreviewSize.getWidth()/getHeight(),而需根据当前屏幕旋转状态动态交换宽高值。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (mPreviewSize != null) {
int previewWidth = mPreviewSize.getWidth();
int previewHeight = mPreviewSize.getHeight();
// 根据当前屏幕方向调整 preview 尺寸语义:确保 preview 宽度对应屏幕宽度方向
int displayRotation = getWindowManager().getDefaultDisplay().getRotation();
if (displayRotation == Surface.ROTATION_90 || displayRotation == Surface.ROTATION_270) {
// 横屏模式下,preview 的宽高需互换(因 sensor 原生为横置)
int tmp = previewWidth;
previewWidth = previewHeight;
previewHeight = tmp;
}
// 按 preview 宽高比约束 TextureView 尺寸:保持内容不裁剪、不拉伸(即 fit-center)
if (width * previewHeight > height * previewWidth) {
// 高度受限 → 按高度反推宽度
width = height * previewWidth / previewHeight;
} else {
// 宽度受限 → 按宽度反推高度
height = width * previewHeight / previewWidth;
}
}
setMeasuredDimension(width, height);
}
⚠️ 注意:此逻辑应在 mPreviewSize 已通过 StreamConfigurationMap.getOutputSizes(SurfaceTexture.class) 获取并确定后调用;且需在 TextureView 的 onSurfaceTextureAvailable() 回调中或之后触发 requestLayout() 以生效。
✅ 精确校准旋转角度(消除方向错乱)
Camera2 的 JPEG_ORIENTATION 仅影响最终 JPEG 图像的 EXIF 元数据,完全不影响预览流(SurfaceTexture)的渲染方向。预览方向错误的根本原因是:TextureView 默认以自身坐标系渲染纹理,而相机输出的 YUV 数据帧方向与屏幕坐标系不一致。
✅ 正确做法是:对 TextureView 本体执行 setRotation() 变换,使其视觉输出与用户感知的设备朝向一致。需结合两个因素计算目标旋转角:
- 设备当前屏幕旋转(Surface.ROTATION_0/90/180/270)
- 相机传感器相对于设备自然方向(通常是竖屏)的固有朝向(可通过 CameraCharacteristics.SENSOR_ORIENTATION 获取,常见值为 90 或 270)
private void updateTextureViewRotation() {
if (textureView == null || mPreviewSize == null) return;
int displayRotation = getWindowManager().getDefaultDisplay().getRotation();
int sensorOrientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
int deviceOrientation = getDeviceOrientation(); // 自定义方法,返回 0/90/180/270(单位:度)
// 计算 TextureView 应旋转的角度:使 preview 内容正立显示
int rotation = (sensorOrientation - deviceOrientation + 360) % 360;
// Camera2 文档推荐:将 rotation 调整为 0/90/180/270 的标准值(更稳定)
switch (rotation) {
case 0: textureView.setRotation(0); break;
case 90: textureView.setRotation(90); break;
case 180: textureView.setRotation(180); break;
case 270: textureView.setRotation(270); break;
default: textureView.setRotation(0); break;
}
}
? 提示:getDeviceOrientation() 可通过 OrientationEventListener 实时监听设备姿态,或在 onConfigurationChanged() 中结合 getResources().getConfiguration().orientation 判断。务必在 TextureView 附加到窗口(onAttachedToWindow)且相机预览会话已创建后调用 updateTextureViewRotation(),并在每次屏幕旋转时重新调用。
? 总结与最佳实践
- 缩放校准本质是比例约束:onMeasure() 必须基于「适配当前屏幕方向的 preview 尺寸比例」进行布局计算,而非硬编码宽高。
- 旋转校准本质是坐标系对齐:不要试图修改 CaptureRequest 中的预览参数来“旋转预览”,而应通过 TextureView.setRotation() 对渲染层做仿射变换。
- 顺序至关重要:先完成 TextureView 尺寸测量与布局(onMeasure → layout),再设置旋转(setRotation),最后开启预览会话(createCaptureSession),否则变换可能被重置。
- 兼容性提醒:部分旧设备(Android 5.0–6.0)可能存在 TextureView 旋转后的触摸坐标偏移问题,如需交互(如聚焦框),建议使用 Matrix 手动映射坐标。
遵循以上方案,即可实现 Camera2 预览在任意设备朝向下均保持比例准确、方向自然,为后续拍照、录像及 AR 功能奠定可靠基础。










