(一)通过CameraX+Jetpack Compose 实现相机预览
前言
目前很多Android项目已经开始基于Jetpack Compose 进行开发了,项目中有个功能,需要扫描二维码,可以直接使用zxing,基于过往开发经验,zxing 的识别效率并没有mlkit 高,因此下面我们一步步完成MlKit版本的 二维码扫描功能。
技术选型
基于以上分析,技术选型如下:
| 功能 | 技术 |
|---|---|
| 页面编写 | Jetpack Compose |
| 二维码识别 | Mlkit |
| 相机画面预览 | CameraX |
最终效果示例:
build.gradle依赖
// CameraX
val cameraxVersion = "1.2.2"
api("androidx.camera:camera-lifecycle:$cameraxVersion")
api("androidx.camera:camera-core:$cameraxVersion")
api("androidx.camera:camera-camera2:$cameraxVersion")
api("androidx.camera:camera-view:$cameraxVersion")
api("androidx.camera:camera-extensions:$cameraxVersion")
// Jetpack Compose 相关依赖
api("androidx.activity:activity-compose:1.10.0")
api(platform("androidx.compose:compose-bom:2025.02.00"))
api("androidx.compose.ui:ui")
api("androidx.compose.ui:ui-graphics")
api("androidx.compose.ui:ui-tooling-preview")
api("androidx.compose.material:material")
api("androidx.compose.material3:material3")
api("androidx.compose.runtime:runtime")
api("androidx.compose.ui:ui-tooling")
因为Jetpack Compose 中没有直接可用的相机组件,因此,需要通过AndroidView 包装Androidx 中的androidx.camera.view.PreviewView 组件,实现CameraView 组件
CameraView.kt
package com.alan.scanbarcode.scan
import android.util.Log
import android.util.Size
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.zIndex
import androidx.core.content.ContextCompat
/**
* 画面预览,拍照
*/
@Composable
fun CameraView() {
// 上下文
val context = LocalContext.current
// 声明周期
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
// 协程域
val cameraProviderFuture = remember {
ProcessCameraProvider.getInstance(context)
}
// 线程池
val executor = remember { ContextCompat.getMainExecutor(context) }
// View 系统CameraX 预览组件
val previewView = remember { PreviewView(context) }
// 预览
val preview = remember {
Preview
.Builder()
.build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
}
// 摄像头选择
val cameraSelector = remember {
CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
}
val cameraProvider = remember { cameraProviderFuture.get() }
// 使用executor 去执行上面的内容
LaunchedEffect(Unit) {
cameraProviderFuture.addListener({
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
)
}, executor)
}
Box(modifier = Modifier.zIndex(1f)) {
AndroidView(
factory = { previewView },
modifier = Modifier
.fillMaxSize()
)
}
}
实现扫描页面
ScanPage.kt
var offset by remember { mutableStateOf(0f) }
// 无限重复动画
val infiniteTransition = rememberInfiniteTransition()
offset = infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 1500, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
).value
Scaffold(modifier = Modifier.fillMaxSize()) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CameraView()
Box(
modifier = Modifier
.size(200.dp)
.zIndex(1000f)
.border(1.dp, color = Color.Blue),
contentAlignment = Alignment.Center,
) {
// 扫描线
Divider(
color = Color.Green.copy(alpha = 0.7f),
thickness = 2.dp,
modifier = Modifier
.fillMaxWidth()
.offset(y = 200.dp * offset - 100.dp)
.shadow(4.dp, shape = RectangleShape)
)
}
}
}