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
)
...
}
我在可组合屏幕中遇到导航撰写 (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
)
...
}