CameraX 无法与 Jetpack Compose 中的导航库一起使用

CameraX not working with navigation library in jetpack compose

我在可组合屏幕中遇到导航撰写 (2.4.0-alpha09) 和 camerax 库 (1.1.0-alpha08) 的问题。如果我只使用相机视图(包装在 AndroidView 中),一切正常并且图像捕获工作正常。但如果我在可组合项中使用 navController 进行导航,图像捕获将无法正常工作并且应用程序会崩溃。

@ExperimentalCoilApi
@Composable
fun CameraScreen(navController: NavController) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current

    Scaffold {
        CameraPreviewScreen(
            modifier = Modifier.fillMaxSize(),
//                navController = navController,
            context = context,
            lifecycleOwner = lifecycleOwner,
            outPutDirectory = getFilesDirectory(context = context),
            onMediaCaptured = { uri ->
//                    navController.navigate(
//                        Screens.AddPostDetails.withArgs(
//                            URLEncoder.encode(
//                                uri.toString(),
//                                StandardCharsets.UTF_8.toString()
//                            )
//                        )
//                    )
            }
        )
    }
}

@ExperimentalCoilApi
@Composable
fun CameraPreviewScreen(
    modifier: Modifier = Modifier,
//    navController: NavController,
    context: Context,
    lifecycleOwner: LifecycleOwner,
    outPutDirectory: File,
    onMediaCaptured: (Uri?) -> Unit
) {
    val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
    val cameraProvider = cameraProviderFuture.get()
    val executor = ContextCompat.getMainExecutor(context)
    var imageCapture: ImageCapture? = null
    var cameraSelector: CameraSelector?
    var camera: Camera? = null
    val previewView = PreviewView(context)
    val preview = Preview.Builder().build().also {
        it.setSurfaceProvider(previewView.surfaceProvider)
    }

    var lensFacing by remember { mutableStateOf(CameraSelector.LENS_FACING_BACK) }
    var flashEnabled by remember { mutableStateOf(false) }
    var flashRes by remember { mutableStateOf(R.drawable.ic_outlined_flash_on) }

    Box {
        AndroidView(
            factory = { ctx ->
                cameraProviderFuture.addListener(
                    {
                        cameraSelector = CameraSelector.Builder()
                            .requireLensFacing(lensFacing)
                            .build()
                        imageCapture = ImageCapture.Builder()
                            .setTargetRotation(previewView.display.rotation)
                            .build()

                        cameraProvider.unbindAll()
                        camera = cameraProvider.bindToLifecycle(
                            lifecycleOwner,
                            cameraSelector as CameraSelector,
                            imageCapture,
                            preview
                        )
                    }, executor
                )
                previewView
            },
            modifier = modifier
        )

        Row(
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
                .align(Alignment.TopStart)
        ) {
            IconButton(onClick = {
                Log.d(TAG, "back clicked")
//                navController.popBackStack()
            }) {
                Icon(
                    modifier = Modifier.iconSize(),
                    imageVector = Icons.Filled.ArrowBack,
                    contentDescription = null,
                    tint = MaterialTheme.colors.surface
                )
            }
        }

        Row(
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
                .clip(RoundedCornerShape(16.dp))
                .background(background, RoundedCornerShape(16.dp))
                .padding(8.dp)
                .align(Alignment.BottomCenter)
        ) {
            IconButton(
                onClick = {
                    camera?.let {
                        if (it.cameraInfo.hasFlashUnit()) {
                            flashEnabled = !flashEnabled
                            flashRes =
                                if (flashEnabled) R.drawable.ic_outlined_flash_off else R.drawable.ic_outlined_flash_on
                            it.cameraControl.enableTorch(flashEnabled)
                        }
                    }
                }) {
                Icon(
                    modifier = Modifier.size(34.dp),
                    painter = painterResource(id = flashRes),
                    contentDescription = null,
                    tint = MaterialTheme.colors.surface
                )
            }

            Button(
                modifier = Modifier
                    .size(70.dp)
                    .background(captureIconColor, CircleShape)
                    .shadow(4.dp, CircleShape)
                    .clip(CircleShape)
                    .border(5.dp, Color.LightGray, CircleShape),
                colors = ButtonDefaults.buttonColors(backgroundColor = captureIconColor),
                onClick = {
                    val imgCapture = imageCapture ?: return@Button
                    val photoFile = File(
                        outPutDirectory,
                        SimpleDateFormat("yyyyMMdd-HHmmss-SSS", Locale.US)
                            .format(System.currentTimeMillis()) + ".jpg"
                    )
                    val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
                    imgCapture.takePicture(
                        outputOptions,
                        executor,
                        object : ImageCapture.OnImageSavedCallback {
                            override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                                Log.d(TAG, "image saved: ${photoFile.path}")
                                onMediaCaptured(Uri.fromFile(photoFile))
                            }

                            override fun onError(exception: ImageCaptureException) {
                                Toast.makeText(
                                    context, "Something went wrong", Toast.LENGTH_SHORT
                                ).show()
                            }
                        }
                    )
                }
            ) {

            }

            IconButton(
                onClick = {
                    lensFacing =
                        if (lensFacing == CameraSelector.LENS_FACING_BACK) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK

                    cameraSelector = CameraSelector.Builder()
                        .requireLensFacing(lensFacing)
                        .build()
                    cameraProvider.unbindAll()
                    cameraProvider.bindToLifecycle(
                        lifecycleOwner,
                        cameraSelector as CameraSelector,
                        imageCapture,
                        preview
                    )
                }) {
                Icon(
                    modifier = Modifier.size(34.dp),
                    painter = painterResource(id = R.drawable.ic_outlined_rotate),
                    contentDescription = null,
                    tint = MaterialTheme.colors.surface
                )
            }
        }
    }
}

确保您拥有相机许可。您可以在“设置”->“应用”->“您的应用”->“权限”->“打开相机权限”中手动设置。

如果您没有在此列表中看到摄像头,则表示您没有在 AndroidMainfest.xml 中添加所需的权限,请查看 this codelab

要请求用户从您的应用启用权限,请使用 Accompanist Permissions


现在开始你的代码。所有可组合函数都是视图生成器,这意味着它们可以在应用程序生命周期内多次调用。您正在创建许多没有 remember 的变量,这意味着它们将在第一次重组时失去它的状态。

AndroidView factory 在创建视图时调用,您应该只在该块内创建 android 视图。

在您的代码中,您是在外部创建它,这意味着在第一次重组后 preview 将有一个无效的 surfaceProvider.

我建议你从 state in compose documentation, including this youtube video 开始,它解释了基本原理。

您的代码应更新为如下所示:

val cameraProviderFuture = remember(context) { ProcessCameraProvider.getInstance(context) }
val cameraProvider = remember(cameraProviderFuture) { cameraProviderFuture.get() }
val executor = remember(context) { ContextCompat.getMainExecutor(context) }
var imageCapture: ImageCapture? by remember { mutableStateOf(null) }
var cameraSelector: CameraSelector? by remember { mutableStateOf(null) }
var camera: Camera? by remember { mutableStateOf(null) }
var preview by remember { mutableStateOf<Preview?>(null) }

var lensFacing by remember { mutableStateOf(CameraSelector.LENS_FACING_BACK) }
var flashEnabled by remember { mutableStateOf(false) }
var flashRes by remember { mutableStateOf(R.drawable.ic_redo) }

Box {
    AndroidView(
        factory = { ctx ->
            val previewView = PreviewView(ctx)
            cameraProviderFuture.addListener(
                {
                    cameraSelector = CameraSelector.Builder()
                        .requireLensFacing(lensFacing)
                        .build()
                    imageCapture = ImageCapture.Builder()
                        .setTargetRotation(previewView.display.rotation)
                        .build()

                    cameraProvider.unbindAll()
                    camera = cameraProvider.bindToLifecycle(
                        lifecycleOwner,
                        cameraSelector as CameraSelector,
                        imageCapture,
                        preview
                    )
                }, executor
            )
            preview = Preview.Builder().build().also {
                it.setSurfaceProvider(previewView.surfaceProvider)
            }
            previewView
        },
        modifier = modifier
    )
    ...
}