Compose - 使用 CameraX

更多详见 CameraX

一、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) {}
        })
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值