You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Camera2Video:实现TextureView视频预览无裁剪无拉伸适配

相机预览适配问题:实现无裁剪无拉伸、比例不符时显示黑边

我基于Google的Camera2Video示例修改了一款横屏APP,支持用户选择视频分辨率、帧率及曝光设置。当前布局中,相机视频显示用的AutoFitTextureView受右侧固定宽度的mainPanel(FrameLayout)和底部固定高度的longTexture(FrameLayout)约束,宽高比随设备屏幕比例变化。用户可选择视频预览的宽高比(如4:3、16:9、2:1),期望预览能完整适配TextureView,无裁剪、无拉伸,仅在比例不一致时显示黑边。但目前预览被缩放填充整个TextureView,虽未拉伸却出现裁剪,无法呈现视频原比例。

当前布局XML代码:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.android.camera2video.AutoFitTextureView
        android:id="@+id/texture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/longTexture"
        android:layout_alignLeft="@id/longTexture"
        android:layout_alignRight="@id/longTexture"
        android:layout_alignTop="@id/mainPanel" />

    <FrameLayout
        android:id="@+id/longTexture"
        android:layout_width="match_parent"
        android:layout_height="75dp"
        android:layout_alignParentBottom="true"
        android:layout_toLeftOf="@id/mainPanel"
        android:background="#0088FF" />

    <FrameLayout
        android:id="@+id/mainPanel"
        android:layout_width="250dp"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:background="#222222"
        android:orientation="horizontal">

        <!-- 按钮和下拉选择器 -->

    </FrameLayout>

</RelativeLayout>

解决方案

1. 调整布局结构,用容器包裹TextureView实现居中黑边

原布局中TextureView被强制填充可用区域,导致预览被裁剪。我们可以用一个FrameLayout作为TextureView的父容器,让TextureView在容器内居中,容器填充原本的可用区域并设置黑色背景(作为黑边):

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 预览容器,填充可用区域,黑色背景作为黑边 -->
    <FrameLayout
        android:id="@+id/previewContainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@id/longTexture"
        android:layout_toLeftOf="@id/mainPanel"
        android:background="#000000">

        <!-- 预览TextureView,居中显示 -->
        <com.example.android.camera2video.AutoFitTextureView
            android:id="@+id/texture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />

    </FrameLayout>

    <FrameLayout
        android:id="@+id/longTexture"
        android:layout_width="match_parent"
        android:layout_height="75dp"
        android:layout_alignParentBottom="true"
        android:layout_toLeftOf="@id/mainPanel"
        android:background="#0088FF" />

    <FrameLayout
        android:id="@+id/mainPanel"
        android:layout_width="250dp"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:background="#222222"
        android:orientation="horizontal">

        <!-- 按钮和下拉选择器 -->

    </FrameLayout>

</RelativeLayout>

2. 修改AutoFitTextureView的测量逻辑,保留预览比例避免裁剪

原AutoFitTextureView的onMeasure方法是强制填充父容器,导致预览比例不符时被裁剪。我们需要修改该方法,使其计算出能放入父容器的最大尺寸,同时保持用户选择的预览比例:

public class AutoFitTextureView extends TextureView {
    private int mRatioWidth = 0;
    private int mRatioHeight = 0;

    public AutoFitTextureView(Context context) {
        this(context, null);
    }

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

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

    /**
     * 设置预览宽高比
     * @param width 比例宽
     * @param height 比例高
     */
    public void setAspectRatio(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("宽高不能为负数");
        }
        mRatioWidth = width;
        mRatioHeight = height;
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);

        if (mRatioWidth == 0 || mRatioHeight == 0) {
            // 未设置比例时,填充父容器
            setMeasuredDimension(parentWidth, parentHeight);
            return;
        }

        // 计算预览比例和父容器比例
        float previewRatio = (float) mRatioWidth / mRatioHeight;
        float parentRatio = (float) parentWidth / parentHeight;

        int finalWidth, finalHeight;
        if (previewRatio > parentRatio) {
            // 预览更宽,按父容器高度计算最大宽度,若超过父容器宽度则按宽度适配高度
            finalHeight = parentHeight;
            finalWidth = Math.round(parentHeight * previewRatio);
            if (finalWidth > parentWidth) {
                finalWidth = parentWidth;
                finalHeight = Math.round(parentWidth / previewRatio);
            }
        } else {
            // 预览更高,按父容器宽度计算最大高度,若超过父容器高度则按高度适配宽度
            finalWidth = parentWidth;
            finalHeight = Math.round(parentWidth / previewRatio);
            if (finalHeight > parentHeight) {
                finalHeight = parentHeight;
                finalWidth = Math.round(parentHeight * previewRatio);
            }
        }

        // 设置TextureView的最终尺寸,确保完整显示预览内容
        setMeasuredDimension(finalWidth, finalHeight);
    }
}

3. 关联用户选择的比例与相机预览尺寸

当用户选择预览比例(如16:9)时,需要从相机支持的预览尺寸中选择最匹配该比例的尺寸,并调用setAspectRatio方法更新TextureView的比例:

// 示例:用户选择16:9比例时
int selectedRatioWidth = 16;
int selectedRatioHeight = 9;
textureView.setAspectRatio(selectedRatioWidth, selectedRatioHeight);

// 从相机支持的尺寸中筛选匹配比例的最优尺寸
Size optimalSize = chooseOptimalSize(cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                .getOutputSizes(SurfaceTexture.class), selectedRatioWidth, selectedRatioHeight);

// 后续将该尺寸用于相机预览配置

chooseOptimalSize方法可参考Camera2Video示例中的实现,调整为优先匹配用户选择的比例。


内容的提问来源于stack exchange,提问作者Interstadial

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 02:59:54