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

Camera2中自定义TextureView宽高并实现正常相机预览的方法

解决Camera2框架下TextureView固定400dp高度预览变形问题

1. 先修正布局,固定预览区域尺寸

首先将布局中TextureView的高度设为400dp,保证显示区域的固定:

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="400dp"
    android:background="@android:color/holo_blue_dark">
    <TextureView
        android:id="@+id/texture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</FrameLayout>

2. 选择匹配TextureView比例的相机预览尺寸

相机输出的预览尺寸比例必须和TextureView显示区域比例匹配,否则会出现拉伸变形。添加以下方法筛选最优尺寸:

private Size chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight) {
    List<Size> matchedSizes = new ArrayList<>();
    // 计算TextureView的显示比例(竖屏场景)
    float textureRatio = (float) textureViewWidth / textureViewHeight;

    for (Size option : choices) {
        // 相机默认输出横屏尺寸,竖屏显示时需交换宽高计算比例
        float previewRatio = (float) option.getHeight() / option.getWidth();
        // 允许微小比例误差,避免无完全匹配的情况
        if (Math.abs(previewRatio - textureRatio) < 0.01) {
            matchedSizes.add(option);
        }
    }

    // 从匹配尺寸中选分辨率最大的
    if (!matchedSizes.isEmpty()) {
        return Collections.max(matchedSizes, (lhs, rhs) -> 
            Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight())
        );
    } 
    // 无匹配尺寸时选最大分辨率的
    else {
        return Collections.max(Arrays.asList(choices), (lhs, rhs) -> 
            Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight())
        );
    }
}

在打开相机前,需等TextureView布局完成后获取实际尺寸,再筛选预览尺寸:

textureView.post(() -> {
    int textureWidth = textureView.getWidth();
    int textureHeight = textureView.getHeight();
    
    CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    try {
        String cameraId = manager.getCameraIdList()[0]; // 这里默认选后置相机,可根据需求调整
        CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
        StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        if (map != null) {
            imageDimension = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), textureWidth, textureHeight);
            openCamera(); // 打开相机的方法,需自行实现
        }
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
});

3. 修正TextureView的矩阵变换逻辑

之前的矩阵计算混淆了屏幕尺寸和相机预览尺寸,且未考虑相机横屏输出到竖屏显示的旋转需求,替换为以下方法:

private void adjustTextureViewTransform() {
    if (imageDimension == null || textureView.getWidth() == 0 || textureView.getHeight() == 0) {
        return;
    }

    int textureWidth = textureView.getWidth();
    int textureHeight = textureView.getHeight();
    Size previewSize = imageDimension;

    float viewRatio = (float) textureWidth / textureHeight;
    float previewRatio = (float) previewSize.getHeight() / previewSize.getWidth(); // 竖屏显示的预览比例

    Matrix matrix = new Matrix();

    if (viewRatio > previewRatio) {
        // TextureView更宽,缩放高度填满显示区域,宽度裁剪
        float scale = (float) textureHeight / previewSize.getWidth();
        float scaledWidth = scale * previewSize.getHeight();
        float dx = (textureWidth - scaledWidth) / 2f;
        matrix.setScale(scale, scale);
        matrix.postTranslate(dx, 0);
    } else {
        // TextureView更高,缩放宽度填满显示区域,高度裁剪
        float scale = (float) textureWidth / previewSize.getHeight();
        float scaledHeight = scale * previewSize.getWidth();
        float dy = (textureHeight - scaledHeight) / 2f;
        matrix.setScale(scale, scale);
        matrix.postTranslate(0, dy);
    }

    // 旋转90度,适配相机横屏输出到竖屏显示的需求
    matrix.postRotate(90, textureWidth / 2f, textureHeight / 2f);

    textureView.setTransform(matrix);
}

4. 触发矩阵变换的时机

在TextureView尺寸变化或相机预览创建完成时调用变换方法:

textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        // 打开相机逻辑
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        adjustTextureViewTransform();
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        closeCamera(); // 自行实现关闭相机的逻辑
        return true;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
});

// 在createCameraPreview的onConfigured回调中也需要调用
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
    if (null == cameraDevice) return;
    cameraCaptureSessions = cameraCaptureSession;
    adjustTextureViewTransform(); // 添加这行
    updatePreview();
}

关键修正点说明

  • 布局固定了预览区域的400dp高度,避免尺寸不确定导致的适配错误
  • 预览尺寸选择时严格匹配TextureView的显示比例,从根源避免拉伸
  • 矩阵计算基于相机预览尺寸和TextureView实际尺寸,同时处理了横屏转竖屏的旋转需求,保证预览内容不变形且居中显示

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 13:35:33