一、Compose 用法
将代码分开写是解耦 UI 和 业务。
1.1 依赖
[versions]
camerax = "1.5.1"
[libraries]
camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" }
camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" }
camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "camerax" }
camera-compose = { group = "androidx.camera", name = "camera-compose", version.ref = "camerax" }
camera-extensions = { group = "androidx.camera", name = "camera-extensions", version.ref = "camerax" }
1.2 UI
@Composable
fun CameraScreen(
viewModel: CameraVM = viewModel()
) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA) { isGranted ->
if (!isGranted) Toast.makeText(context,"未授予相机权限", Toast.LENGTH_LONG).show()
}
//直接拉起权限
LaunchedEffect(Unit) { cameraPermissionState.launchPermissionRequest() }
//根据有无权限显示不同页面
if (cameraPermissionState.status.isGranted) {
//镜头切换会重新绑定
LaunchedEffect(viewModel.cameraLens) { viewModel.bindToCamera(context,lifecycleOwner) }
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Content(viewModel.surfaceRequest)
Button({
viewModel.switchCamera()
}) { Text("切换镜头") }
}
} else {
Text("请授予相机权限后使用")
}
}
@Composable
fun Content(
surfaceRequest: SurfaceRequest?,
modifier: Modifier = Modifier,
) {
//很简洁,只有一个可组合项,接收SurfaceRequest并进行渲染
surfaceRequest?.let { request ->
CameraXViewfinder(
modifier = modifier.height(400.dp).width(300.dp),
surfaceRequest = request
)
}
}
1.3 ViewModel
class CameraVM : ViewModel() {
//用来更新预览的状态流,它的更新来自 CameraX 的回调
var surfaceRequest by mutableStateOf<SurfaceRequest?>(null)
private set
private val previewUseCase = Preview.Builder().build().apply {
setSurfaceProvider { request ->
surfaceRequest = request
}
}
//让UI可读,观察镜头变化,重新调用绑定才生效
var cameraLens by mutableStateOf(true)
private set
//重写getter才会随镜头变化返回不同值,而等号只是初始化
private val cameraSelector
get() = if (cameraLens) CameraSelector.DEFAULT_BACK_CAMERA else CameraSelector.DEFAULT_FRONT_CAMERA
private val imageCaptureUseCase = ImageCapture.Builder().build()
//绑定生命周期和用例
suspend fun bindToCamera(context: Context, lifecycleOwner: LifecycleOwner) {
//挂起的方式获取实例(不需要设置Listener并传入Runnable和Executor了)
val cameraProvider = ProcessCameraProvider.awaitInstance(context)
// cameraProvider.unbindAll() //暂未出现需要调用解绑才能用的情况,镜头切换正常
val camera = cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
previewUseCase,
imageCaptureUseCase
)
//释放资源
try {
awaitCancellation()
} finally {
cameraProvider.unbindAll()
}
}
//切换镜头
fun switchCamera() {
cameraLens = !cameraLens
}
//拍照
fun takePic(context: Context) {
val contentResolver = context.contentResolver
val time = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(System.currentTimeMillis())
val fileName = "CameraX_${time}"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
}
val outputOptions = ImageCapture.OutputFileOptions
.Builder(contentResolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
.build()
imageCaptureUseCase.takePicture(outputOptions, context.mainExecutor, object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
//可以拿到图片的Uri更新状态给UI(Coil能直接显示,也可以通过流转为Bitmap使用)
outputFileResults.savedUri?.let {
contentResolver.openOutputStream(it)?.use { outputStream -> }
}
}
override fun onError(exception: ImageCaptureException) {}
})
}
}
639

被折叠的 条评论
为什么被折叠?



