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




