You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

CameraX开发问题:如何设置PreviewView背景并解决相机切换时的黑屏问题

解决相机切换时黑屏,显示模糊最后一帧的问题

你找的方向没错,但问题出在PreviewView的背景会被其内部的SurfaceView/TextureView完全覆盖,再加上相机解绑与绑定的时机差,导致你的背景设置根本没机会展示出来。我们可以通过给PreviewView添加临时覆盖层的方式来解决这个问题,具体步骤如下:

步骤1:准备临时覆盖层

先在布局里给mPreviewView套一个FrameLayout,并添加一个用来显示模糊帧的ImageView作为覆盖层:

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.camera.view.PreviewView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <ImageView
        android:id="@+id/blurOverlay"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:visibility="gone"/>
</FrameLayout>

步骤2:修改相机切换逻辑

核心思路是:在解绑旧相机前获取当前预览帧并做模糊处理,显示覆盖层;等新相机绑定完成、预览画面正常渲染后,再隐藏覆盖层。同时要注意避免主线程被模糊计算阻塞:

private ImageView mBlurOverlay;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mPreviewView = findViewById(R.id.previewView);
    mBlurOverlay = findViewById(R.id.blurOverlay);
    // 其他初始化代码...
}

void startCamera(final boolean forced){
    if(!forced && camera!=null) return;

    // 1. 获取当前预览最后一帧,异步处理模糊
    Bitmap currentFrame = mPreviewView.getBitmap();
    if (currentFrame != null) {
        new AsyncTask<Void, Void, Bitmap>() {
            @Override
            protected Bitmap doInBackground(Void... voids) {
                return blurRenderScript(currentFrame, 25); // 可调整模糊半径(最大25)
            }

            @Override
            protected void onPostExecute(Bitmap blurredBitmap) {
                mBlurOverlay.setImageBitmap(blurredBitmap);
                mBlurOverlay.setVisibility(View.VISIBLE);
            }
        }.execute();
    }

    Preview preview = new Preview.Builder().build();
    CameraSelector cameraSelector = new CameraSelector.Builder()
            .requireLensFacing(this.cameraSelector)
            .build();
    ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
    ImageCapture imageCapture = new ImageCapture.Builder()
            .setTargetRotation(getWindowManager().getDefaultDisplay().getRotation())
            .build();

    preview.setSurfaceProvider(mPreviewView.getSurfaceProvider());

    // 2. 解绑旧相机
    cameraProvider.unbindAll();

    // 3. 绑定新相机,监听预览就绪事件
    camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis, imageCapture);

    // 当预览Surface准备完成后,延迟隐藏覆盖层(确保预览画面已渲染)
    preview.setSurfaceProvider((request) -> {
        mPreviewView.getSurfaceProvider().onSurfaceRequested(request);
        mPreviewView.postDelayed(() -> {
            mBlurOverlay.setVisibility(View.GONE);
            // 回收Bitmap避免内存泄漏
            if (currentFrame != null && !currentFrame.isRecycled()) {
                currentFrame.recycle();
            }
        }, 100);
    });

    mPreviewView.setOnTouchListener(this);
    start_auto_focus();
}

// 优化后的模糊函数,增加内存回收逻辑
private Bitmap blurRenderScript(Bitmap smallBitmap, int radius) {
    final float defaultBitmapScale = 0.2f; // 适度提高缩放比例,保证模糊效果自然
    int width = Math.round(smallBitmap.getWidth() * defaultBitmapScale);
    int height = Math.round(smallBitmap.getHeight() * defaultBitmapScale);
    Bitmap inputBitmap = Bitmap.createScaledBitmap(smallBitmap, width, height, false);
    Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
    
    RenderScript renderScript = RenderScript.create(this);
    ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
    Allocation tmpIn = Allocation.createFromBitmap(renderScript, inputBitmap);
    Allocation tmpOut = Allocation.createFromBitmap(renderScript, outputBitmap);
    
    theIntrinsic.setRadius(Math.min(radius, 25)); // ScriptIntrinsicBlur的radius上限为25
    theIntrinsic.setInput(tmpIn);
    theIntrinsic.forEach(tmpOut);
    tmpOut.copyTo(outputBitmap);
    
    // 回收临时资源
    inputBitmap.recycle();
    renderScript.destroy();
    return outputBitmap;
}

关键注意点

  • 用覆盖层替代背景:PreviewView的内部Surface是前景元素,背景会被完全遮挡,只有放在上层的ImageView才能正常展示模糊帧。
  • 异步处理模糊:模糊计算属于耗时操作,放在异步任务里可以避免主线程卡顿。
  • 内存回收:Bitmap容易引发内存泄漏,记得在使用完成后及时回收临时对象。
  • 延迟隐藏覆盖层:相机绑定完成后,预览画面需要短暂的渲染时间,延迟100ms左右隐藏覆盖层可以避免出现闪黑。

如果你不想在布局里提前定义ImageView,也可以在代码里动态创建并添加到PreviewView的父容器中,效果完全一致。

内容的提问来源于stack exchange,提问作者Son of Stackoverflow

火山引擎 最新活动