本文详解如何在 android camera2 api 中正确配置 textureview 的宽高比和屏幕方向,解决预览图像拉伸、旋转错位及倒置问题,涵盖动态尺寸测量、设备方向适配与纹理视图旋转控制。
本文详解如何在 android camera2 api 中正确配置 textureview 的宽高比和屏幕方向,解决预览图像拉伸、旋转错位及倒置问题,涵盖动态尺寸测量、设备方向适配与纹理视图旋转控制。
在基于 Camera2 构建相机预览时,TextureView 是常用载体,但其默认行为不会自动适配相机输出流的原始宽高比(aspect ratio)和设备物理朝向,导致常见问题:图像水平拉伸、竖屏预览偏转 ±90°、倒置(180°翻转)等。根本原因在于——相机硬件输出的预览尺寸(如 1920×1080)是固定方向的(通常以传感器自然方向为基准),而 UI 容器(TextureView)需根据设备当前屏幕方向(ROTATION_0/90/180/270)和应用窗口布局进行双重对齐。
✅ 正确设置宽高比:动态匹配预览尺寸
关键不是硬编码比例,而是依据 CaptureRequest.Builder 所用的 PreviewSize(即 StreamConfigurationMap.getOutputSizes(SurfaceTexture.class) 返回的最佳尺寸)实时计算。尤其注意:相机预览尺寸本身不随设备旋转而改变,但 UI 布局方向会变。因此,在 onMeasure() 中需先判断当前屏幕方向,再交换宽高逻辑以匹配“视觉上的预览比例”。
@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();
// 根据当前屏幕方向调整预览尺寸的“视觉宽高”映射
// Camera sensor 输出是固定的,但用户看到的“宽”取决于屏幕朝向
Display display = getWindowManager().getDefaultDisplay();
int rotation = display.getRotation();
boolean isLandscape = (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180)
? width > height : width < height;
if (isLandscape) {
// 横屏时,将 previewHeight 视为“视觉宽度”,previewWidth 视为“视觉高度”
// 即交换用于计算的宽高,使 TextureView 保持与预览一致的视觉比例
int tmp = previewWidth;
previewWidth = previewHeight;
previewHeight = tmp;
}
// 等比缩放:保持 Aspect Ratio,居中裁剪(类似 CENTER_CROP)
if (width * previewHeight > height * previewWidth) {
// 高度受限 → 按高度反推宽度
width = height * previewWidth / previewHeight;
} else {
// 宽度受限 → 按宽度反推高度
height = width * previewHeight / previewWidth;
}
}
setMeasuredDimension(width, height);
}⚠️ 注意事项:
- mPreviewSize 必须在 configureOutputs() 或 createCaptureSession() 前已通过 StreamConfigurationMap 获取并缓存;
- 不要直接使用 getResources().getConfiguration().orientation,它返回的是 ORIENTATION_LANDSCAPE/PORTRAIT,滞后且不可靠;应使用 Display.getRotation() 获取实时物理旋转;
- 此方案实现的是 “居中裁剪”式适配(类似 ImageView 的 CENTER_CROP),确保无拉伸、无黑边,但可能裁切边缘内容——这是 Camera2 预览的标准实践。
✅ 正确处理旋转:TextureView 旋转优于 JPEG_ORIENTATION
许多开发者误以为设置 JPEG_ORIENTATION 就能修正预览方向,但该参数仅影响 JPEG 编码输出(如拍照结果),对 SurfaceTexture 预览流完全无效。预览方向必须由 TextureView 自身的 setRotation() 控制,并与设备 Display.Rotation 和相机传感器方向(CameraCharacteristics.SENSOR_ORIENTATION)协同计算。
标准校准公式如下(适用于大多数设备):
private void updateTextureViewRotation() {
if (textureView == null || mPreviewSize == null) return;
Display display = getWindowManager().getDefaultDisplay();
int deviceRotation = display.getRotation(); // 0, 90, 180, 270
int sensorOrientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); // e.g., 90 or 270
// Camera2 要求:TextureView 旋转角度 = (sensorOrientation - deviceRotation + 360) % 360
// 但需注意:TextureView 旋转后,触摸坐标系也会旋转,若需交互需额外校准
int rotationCompensation = (sensorOrientation - deviceRotation + 360) % 360;
// 对于前置摄像头,还需镜像翻转(仅视觉,不影响预览流数据)
boolean isFrontFacing = mCharacteristics.get(CameraCharacteristics.LENS_FACING)
== CameraCharacteristics.LENS_FACING_FRONT;
if (isFrontFacing) {
rotationCompensation = (rotationCompensation + 180) % 360;
textureView.setScaleX(-1f); // 水平镜像(仅视觉)
} else {
textureView.setScaleX(1f);
}
textureView.setRotation(rotationCompensation);
}✅ 最佳实践建议:
- 在 onResume()、屏幕旋转监听(onConfigurationChanged)及 onDisplayChanged 回调中调用 updateTextureViewRotation();
- 若使用 CameraCaptureSession.setRepeatingRequest(),确保旋转更新在 session 创建之后、首帧渲染之前完成;
- 前置摄像头需额外水平镜像(setScaleX(-1f)),否则自拍镜像异常;
- JPEG_ORIENTATION 仍需在拍照时单独设置(用于 EXIF 方向标记),与预览无关。
✅ 总结:三步闭环校准
| 步骤 | 关键动作 | 目标 |
|---|---|---|
| 1. 尺寸对齐 | onMeasure() 动态计算宽高,按 Display.getRotation() 适配预览尺寸视觉比例 | 消除拉伸、黑边,实现无损居中预览 |
| 2. 视图旋转 | TextureView.setRotation() 结合 SENSOR_ORIENTATION 与 Display.Rotation 计算补偿角 | 确保预览画面始终“正立”,方向与用户视角一致 |
| 3. 镜像处理(可选) | 前置摄像头 setScaleX(-1f) + 旋转补偿微调 | 实现符合直觉的自拍镜像效果 |
遵循上述逻辑,即可彻底解决 Camera2 预览中长期困扰开发者的缩放失真与方向错乱问题,构建专业、稳定、跨机型兼容的相机界面。

















