使用UseCaseGroupBuilder+ViewPort时CameraX无预览但可拍照求助
CameraX使用UseCaseGroupBuilder后预览不显示但拍照正常的解决方案
问题场景
为了让预览和拍摄图像拥有相同的FOV(视野)或比例,我使用UseCaseGroupBuilder配合ViewPort实现同步,但遇到CameraX无法显示预览却能正常拍照的问题。直接绑定Preview和ImageCapture时能正常运行,但预览与拍摄图像比例不一致,无法实现所见即所得。
我的startCamera函数:
private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() val preview = androidx.camera.core.Preview.Builder() .build().also { it.setSurfaceProvider(mPreviewView.surfaceProvider) } imageCapture = ImageCapture.Builder() .setFlashMode(DEFAULT_FLASH_MODE) .build() // Select default back camera. val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { cameraProvider.unbindAll() val viewPort: ViewPort = ViewPort.Builder( Rational( mPreviewView.width, mPreviewView.height ), mPreviewView.display.rotation ).setScaleType(ViewPort.FILL_CENTER).build() val useCaseGroupBuilder: UseCaseGroup.Builder = UseCaseGroup.Builder() .addUseCase(imageCapture ?: return) .addUseCase(preview) .setViewPort( viewPort ) val camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCaseGroupBuilder.build() ) mFlash.setBackgroundResource( if (imageCapture?.flashMode == FLASH_MODE_OFF) { R.drawable.sm_flash_off } else { R.drawable.sm_flash_on } ) mFlash.setOnClickListener { if (camera.cameraInfo.hasFlashUnit()) { onFlashClick(imageCapture) } else { runOnUiThread { Toast.makeText(this, "Flash is not available currently", Toast.LENGTH_LONG) .show() } } } mButton.setOnClickListener { takePhoto() } } catch (e: Exception) { Log.e(logTag, "UseCase binding failed", e) } }
XML关键部分:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".SmartMerchCameraActivity" tools:visibility="visible"> <androidx.camera.view.PreviewView android:id="@+id/preview_view" android:layout_alignParentEnd="true" android:layout_alignParentStart="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" />
问题根源
创建ViewPort时直接使用mPreviewView.width和mPreviewView.height,但此时PreviewView还未完成布局测量,这两个值为0,导致ViewPort的比例参数无效,CameraX无法正确渲染预览画面;而拍照功能不受影响是因为ImageCapture会自动使用默认尺寸配置。
修复步骤
1. 监听PreviewView布局完成事件
确保获取到真实宽高后再创建ViewPort并绑定用例,避免使用未初始化的尺寸值。
2. 调整SurfaceProvider设置时机
将preview.setSurfaceProvider()移到布局完成后执行,确保预览组件已准备就绪。
修改后的代码示例
private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() val preview = androidx.camera.core.Preview.Builder() .build() imageCapture = ImageCapture.Builder() .setFlashMode(DEFAULT_FLASH_MODE) .build() val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA // 监听PreviewView布局完成,获取真实尺寸 mPreviewView.doOnLayout { v -> try { cameraProvider.unbindAll() // 使用布局完成后的真实宽高创建ViewPort val viewPort = ViewPort.Builder( Rational(v.width, v.height), mPreviewView.display.rotation ).setScaleType(ViewPort.FILL_CENTER).build() val useCaseGroup = UseCaseGroup.Builder() .addUseCase(imageCapture ?: return@doOnLayout) .addUseCase(preview) .setViewPort(viewPort) .build() val camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCaseGroup ) // 布局完成后再设置SurfaceProvider preview.setSurfaceProvider(mPreviewView.surfaceProvider) // 闪光灯逻辑保持不变 mFlash.setBackgroundResource( if (imageCapture?.flashMode == FLASH_MODE_OFF) { R.drawable.sm_flash_off } else { R.drawable.sm_flash_on } ) mFlash.setOnClickListener { if (camera.cameraInfo.hasFlashUnit()) { onFlashClick(imageCapture) } else { runOnUiThread { Toast.makeText(this, "Flash is not available currently", Toast.LENGTH_LONG) .show() } } } mButton.setOnClickListener { takePhoto() } } catch (e: Exception) { Log.e(logTag, "UseCase binding failed", e) } } }
额外说明
- 若
PreviewView使用wrap_content,CameraX会根据相机支持的尺寸自动调整预览大小,但结合ViewPort时必须确保获取布局后的真实尺寸,否则会出现预览不显示的问题。 - 使用
UseCaseGroup绑定预览和拍照用例后,CameraX会强制两者使用相同的FOV和比例,实现所见即所得的拍摄效果。
内容的提问来源于stack exchange,提问作者UniKinG
相关产品推荐
相关产品推荐

