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

如何用Jetpack Compose结合CameraX自定义相机预览并处理帧

Jetpack Compose + CameraX 自定义预览组件实现方案

核心思路

放弃CameraX自带的PreviewView,改用ImageAnalysis用例抓取相机原始帧,在帧分析器中完成自定义图像处理(比如滤镜、裁剪、灰度化等),再将处理后的图像数据传递到Composable组件中展示。全程基于Compose实现,无需依赖XML布局。

步骤1:添加依赖

在app/build.gradle.kts中引入CameraX核心及Compose扩展库:

dependencies {
    // CameraX 核心库
    implementation("androidx.camera:camera-core:1.3.2")
    implementation("androidx.camera:camera-camera2:1.3.2")
    // CameraX Compose 适配扩展
    implementation("androidx.camera:camera-compose:1.3.2")
    // Kotlin 核心扩展(处理图像转换)
    implementation("androidx.core:core-ktx:1.12.0")
}

步骤2:相机权限处理

使用Compose的权限API请求相机权限:

val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA)
LaunchedEffect(Unit) {
    if (!cameraPermissionState.hasPermission) {
        cameraPermissionState.launchPermissionRequest()
    }
}

步骤3:自定义帧处理与预览组件

3.1 定义状态存储处理后的图像

用MutableState保存处理后的Bitmap,供Composable实时渲染:

val processedBitmap = remember { mutableStateOf<Bitmap?>(null) }

3.2 初始化CameraX并绑定图像分析用例

通过DisposableEffect管理CameraX生命周期,确保组件销毁时释放资源:

val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val coroutineScope = rememberCoroutineScope()

DisposableEffect(lifecycleOwner) {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
    val imageAnalyzer = ImageAnalysis.Builder()
        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) // 仅保留最新帧,避免卡顿
        .build()
        .also {
            it.setAnalyzer(coroutineScope) { image ->
                // 1. 将CameraX输出的YUV格式图像转为Bitmap
                val rawBitmap = image.toBitmap()
                // 2. 自定义图像处理:示例为灰度化
                val handledBitmap = rawBitmap.toGrayScale()
                // 3. 切换到主线程更新Compose状态
                coroutineScope.launch(Dispatchers.Main) {
                    processedBitmap.value = handledBitmap
                }
                // 必须释放图像资源,否则CameraX会停止输出新帧
                image.close()
            }
        }

    cameraProviderFuture.addListener({
        val cameraProvider = cameraProviderFuture.get()
        try {
            // 先解绑所有已绑定的用例
            cameraProvider.unbindAll()
            // 绑定相机与图像分析用例
            cameraProvider.bindToLifecycle(
                lifecycleOwner,
                CameraSelector.DEFAULT_BACK_CAMERA,
                imageAnalyzer
            )
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }, ContextCompat.getMainExecutor(context))

    // 组件销毁时解绑相机资源
    onDispose {
        cameraProviderFuture.get().unbindAll()
    }
}

3.3 图像转换工具方法

添加扩展函数实现YUV转Bitmap、灰度化处理:

// 将CameraX的YUV图像转为Bitmap
fun Image.toBitmap(): Bitmap {
    val yuvImage = YuvImage(
        planes[0].buffer,
        ImageFormat.NV21,
        width,
        height,
        null
    )
    val outputStream = ByteArrayOutputStream()
    yuvImage.compressToJpeg(Rect(0, 0, width, height), 100, outputStream)
    val imageBytes = outputStream.toByteArray()
    return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
}

// Bitmap灰度化处理
fun Bitmap.toGrayScale(): Bitmap {
    val grayBitmap = Bitmap.createBitmap(width, height, config)
    val canvas = Canvas(grayBitmap)
    val paint = Paint().apply {
        colorFilter = ColorMatrixColorFilter(ColorMatrix().apply { setSaturation(0f) })
    }
    canvas.drawBitmap(this, 0f, 0f, paint)
    return grayBitmap
}

3.4 自定义预览Composable

用Image组件展示处理后的图像,同时添加加载状态提示:

@Composable
fun CustomCameraPreview(processedBitmap: State<Bitmap?>) {
    Box(modifier = Modifier.fillMaxSize()) {
        processedBitmap.value?.let { bitmap ->
            Image(
                bitmap = bitmap.asImageBitmap(),
                contentDescription = "自定义相机预览",
                modifier = Modifier.fillMaxSize(),
                contentScale = ContentScale.Crop
            )
        } ?: run {
            Text(
                text = "加载相机预览中...",
                modifier = Modifier.align(Alignment.Center),
                color = Color.White
            )
        }
    }
}

步骤4:整合所有代码

将上述模块整合到主Composable中:

@Composable
fun CameraScreen() {
    val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA)
    val processedBitmap = remember { mutableStateOf<Bitmap?>(null) }
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    val coroutineScope = rememberCoroutineScope()

    // 请求相机权限
    LaunchedEffect(Unit) {
        if (!cameraPermissionState.hasPermission) {
            cameraPermissionState.launchPermissionRequest()
        }
    }

    if (cameraPermissionState.hasPermission) {
        // 初始化CameraX
        DisposableEffect(lifecycleOwner) {
            val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
            val imageAnalyzer = ImageAnalysis.Builder()
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()
                .also {
                    it.setAnalyzer(coroutineScope) { image ->
                        val rawBitmap = image.toBitmap()
                        val handledBitmap = rawBitmap.toGrayScale()
                        coroutineScope.launch(Dispatchers.Main) {
                            processedBitmap.value = handledBitmap
                        }
                        image.close()
                    }
                }

            cameraProviderFuture.addListener({
                val cameraProvider = cameraProviderFuture.get()
                try {
                    cameraProvider.unbindAll()
                    cameraProvider.bindToLifecycle(
                        lifecycleOwner,
                        CameraSelector.DEFAULT_BACK_CAMERA,
                        imageAnalyzer
                    )
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }, ContextCompat.getMainExecutor(context))

            onDispose {
                cameraProviderFuture.get().unbindAll()
            }
        }

        // 展示自定义预览
        CustomCameraPreview(processedBitmap)
    } else {
        // 权限被拒绝提示
        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            Text(text = "需要相机权限才能使用", color = Color.Red)
        }
    }
}

关键注意事项

  • 帧资源释放:必须调用image.close(),否则CameraX会停止输出新帧,导致预览卡顿。
  • 线程切换:ImageAnalysis的分析器运行在后台线程,更新Compose状态必须切换到主线程。
  • 生命周期绑定:用DisposableEffect将CameraX与LifecycleOwner绑定,避免组件销毁后相机资源泄漏。
  • 性能优化:示例中的YUV转Bitmap是基础实现,若需高性能可考虑使用RenderScript或GPU加速处理图像。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 19:03:33