当 Android 相机设置了某个分辨率后预览画面出现变形拉伸,可以通过以下4种方法进行处理:

1. 选择合适的预览分辨率

  • 获取支持的预览尺寸:通过 Camera.Parameters 类的 getSupportedPreviewSizes() 方法获取相机支持的所有预览分辨率列表,然后从中选择一个与屏幕宽高比最接近的分辨率来设置为相机的预览分辨率.

  • 计算最佳预览尺寸:编写方法来计算最佳的预览尺寸,通常是根据屏幕的宽高比与相机支持的预览尺寸的宽高比进行对比,找到差值最小的那个预览尺寸。以下是一个示例方法:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        final double aspect_tolerance = 0.1;
        double targetRatio = (double) w / h;
        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;
        int targetHeight = h;
        // 尝试找到匹配宽高比和尺寸的预览尺寸
        for (Camera.Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > aspect_tolerance)
                continue;
            int tempDiff = Math.abs(size.height - targetHeight);
            if (tempDiff < minDiff) {
                optimalSize = size;
                minDiff = tempDiff;
            }
        }
        // 如果找不到匹配宽高比的尺寸,则忽略宽高比要求,仅根据高度差值选择
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }
    

2. 设置预览画面的比例

如果使用的是 SurfaceViewTextureView 来显示相机预览画面,可以在布局文件或代码中设置其宽高比,使其与相机预览分辨率的宽高比一致,从而避免画面变形3910. 例如,对于 SurfaceView,可以在布局文件中这样设置:

1
2
3
4
5
6
<SurfaceView
    android:id="@+id/surfaceView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:layout_gravity="center"/>

或者在代码中动态设置:

1
2
3
4
5
SurfaceView surfaceView = findViewById(R.id.surfaceView);
ViewGroup.LayoutParams layoutParams = surfaceView.getLayoutParams();
layoutParams.width = screenWidth;
layoutParams.height = (int) (screenWidth * previewRatio);
surfaceView.setLayoutParams(layoutParams);

3. 根据相机分辨率调整布局

  • 当相机分辨率与屏幕分辨率不一致时,可以根据两者的比例关系来调整显示相机预览画面的布局大小和位置,确保画面能够完整且正常地显示在屏幕上.

  • 可以通过获取相机预览的宽高和屏幕的宽高,计算出合适的布局参数,然后设置给相应的布局视图 。例如:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    private void adjustPreviewLayout() {
        int surfaceViewWidth = getWidth();
        int surfaceViewHeight = getHeight();
        int cameraWidth = cameraPreviewSize.width;
        int cameraHeight = cameraPreviewSize.height;
    
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) previewRelativeLayout.getLayoutParams();
    
        if (surfaceViewWidth * cameraHeight == surfaceViewHeight * cameraWidth) {
            layoutParams.width = surfaceViewWidth;
            layoutParams.height = surfaceViewHeight;
        } else if (surfaceViewWidth * cameraHeight > surfaceViewHeight * cameraWidth) {
            layoutParams.width = surfaceViewWidth;
            layoutParams.height = (int) (cameraHeight * (float) surfaceViewWidth / cameraWidth);
            layoutParams.topMargin = (surfaceViewHeight - layoutParams.height) / 2;
            layoutParams.bottomMargin = (surfaceViewHeight - layoutParams.height) / 2;
        } else {
            layoutParams.width = (int) (cameraWidth * (float) surfaceViewHeight / cameraHeight);
            layoutParams.height = surfaceViewHeight;
            layoutParams.leftMargin = (surfaceViewWidth - layoutParams.width) / 2;
            layoutParams.rightMargin = (surfaceViewWidth - layoutParams.width) / 2;
        }
    
        previewRelativeLayout.setLayoutParams(layoutParams);
        previewRelativeLayout.requestLayout();
    }
    

4. 使用自定义预览控件

自定义继承自 TextureViewSurfaceView 的预览控件,在其中添加逻辑来自动调整预览画面的大小和比例,以适应不同的相机分辨率和屏幕方向变化. 例如,可以创建一个 AutoFitTextureView 类,在其中重写 onMeasure 方法,根据设定的宽高比来计算和调整视图的大小:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class AutoFitTextureView extends TextureView {
    private int ratioWidth = 0;
    private int ratioHeight = 0;

    public AutoFitTextureView(Context context) {
        super(context);
    }

    public AutoFitTextureView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public AutoFitTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setAspectRatio(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Width or height cannot be negative.");
        }
        ratioWidth = height;
        ratioHeight = width;
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (0 == ratioWidth || 0 == ratioHeight) {
            setMeasuredDimension(width, height);
        } else {
            if (width < height * ratioWidth / ratioHeight) {
                setMeasuredDimension(width, width * ratioHeight / ratioWidth);
            } else {
                setMeasuredDimension(height * ratioWidth / ratioHeight, height);
            }
        }
    }
}

然后在布局文件中使用这个自定义预览控件,并在代码中根据相机预览尺寸设置其宽高比