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

Android Camera2Video预览问题:自定义FrameLayout中偏移与拉伸优化需求

Hey there! Let’s work through this camera preview issue you’re facing with Camera2VideoFragment, AutoFitTextureView, and your custom FrameLayout. I’ve wrestled with similar scaling/alignment problems before, so here’s a practical, step-by-step fix to get that centered, letterboxed preview (no more stretching or top-left offsets):

Root Cause Breakdown

The problem happens because:

  • The camera’s native preview aspect ratio doesn’t match your custom FrameLayout’s ratio.
  • The default AutoFitTextureView only adjusts its own size to fit the parent, but doesn’t handle centering or letterboxing (hence the top-left offset when you force it to fill the frame, leading to distortion).

Step 1: Rewrite AutoFitTextureView’s Scaling Logic

The core fix lies in modifying AutoFitTextureView’s onMeasure method to calculate the correct scaled size (maintaining the camera’s aspect ratio) and then centering itself within the FrameLayout using margins. Replace your existing onMeasure with this:

@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) {
        // Fallback if ratio isn't set yet
        setMeasuredDimension(parentWidth, parentHeight);
        return;
    }

    // Calculate the correct size to preserve the camera's aspect ratio
    float cameraRatio = (float) mRatioWidth / mRatioHeight;
    float parentRatio = (float) parentWidth / parentHeight;

    int scaledWidth;
    int scaledHeight;

    if (cameraRatio > parentRatio) {
        // Camera is wider than parent; scale based on parent width
        scaledWidth = parentWidth;
        scaledHeight = Math.round(parentWidth / cameraRatio);
    } else {
        // Camera is taller than parent; scale based on parent height
        scaledHeight = parentHeight;
        scaledWidth = Math.round(parentHeight * cameraRatio);
    }

    // Set the TextureView's measured size to the scaled dimensions
    setMeasuredDimension(scaledWidth, scaledHeight);

    // Center the TextureView within the parent FrameLayout using margins
    ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) getLayoutParams();
    params.leftMargin = (parentWidth - scaledWidth) / 2;
    params.topMargin = (parentHeight - scaledHeight) / 2;
    setLayoutParams(params);
}

What this does:

  1. It calculates the largest possible size for the preview that fits your FrameLayout without stretching the camera’s aspect ratio.
  2. It adds left/top margins to center the preview, creating natural black bars (letterboxing) where needed.
  3. Eliminates the top-left offset by ensuring the preview is anchored in the center.

Step 2: Ensure You’re Choosing the Right Preview Size

In Camera2VideoFragment, the chooseOptimalSize method needs to prioritize preview sizes that match the camera’s native aspect ratio. Double-check your implementation to make sure it selects sizes that align with the camera’s output ratio (not just arbitrary sizes). Here’s a refined version:

private static Size chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight,
                                     int maxWidth, int maxHeight, Size aspectRatio) {
    List<Size> validSizes = new ArrayList<>();
    int targetWidth = aspectRatio.getWidth();
    int targetHeight = aspectRatio.getHeight();

    for (Size option : choices) {
        // Only consider sizes that match the camera's aspect ratio
        if (option.getWidth() * targetHeight == option.getHeight() * targetWidth) {
            // Filter sizes that fit within the parent bounds and are large enough for the preview
            if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
                option.getWidth() >= textureViewWidth && option.getHeight() >= textureViewHeight) {
                validSizes.add(option);
            }
        }
    }

    // Pick the smallest valid size (balances performance and quality)
    if (!validSizes.isEmpty()) {
        return Collections.min(validSizes, new CompareSizesByArea());
    }

    // Fallback if no valid sizes found
    Log.e(TAG, "No optimal preview size found; using first available");
    return choices[0];
}

Key note:

Make sure you’re passing the correct aspect ratio (from the camera’s characteristics) to this method. You can get it like this when setting up the camera:

CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
assert map != null;
Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
// Use the camera's native aspect ratio
Size aspectRatio = outputSizes[0]; // Or calculate from the largest size for better accuracy

Step 3: Verify Your Layout Setup

Ensure your custom FrameLayout and AutoFitTextureView are configured correctly in XML:

<com.yourpackage.YourCustomFrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.yourpackage.AutoFitTextureView
        android:id="@+id/texture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</com.yourpackage.YourCustomFrameLayout>
  • Setting wrap_content for the TextureView lets our onMeasure logic take full control of its size.
  • The custom FrameLayout should fill the space you want the preview to occupy (use match_parent or fixed dimensions as needed).

Step 4: Double-Check Camera Session Configuration

When initializing the camera preview surface, make sure you set the default buffer size to the optimal preview size you selected:

SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
// Match the buffer size to our chosen preview size
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface previewSurface = new Surface(texture);

This ensures the camera sends frames in the correct resolution, preventing any unexpected scaling or offsets.


After implementing these changes, your preview should now center perfectly within the custom FrameLayout, with black bars on the sides/top/bottom (no stretching, no top-left offset). Test it across different devices to make sure it works with various screen ratios!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 06:26:44