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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 08:13:19