如何在Jetpack Compose中为二维码扫描器添加聚焦小方块
在Jetpack Compose中实现二维码扫描聚焦指示方块
核心实现思路
通过Box布局将相机预览层与自定义绘制的聚焦方块分层叠加,利用Canvas绘制扫描到的二维码边界框,同时处理相机坐标到UI坐标的适配转换,解决预览缩放/裁剪导致的位置偏移问题。
完整代码示例
1. 扫描器主布局(整合相机预览与聚焦指示)
@Composable fun QrScannerScreen() { val context = LocalContext.current val cameraController = remember { CameraController().apply { setEnabledUseCases(CameraController.UseCase.Preview) } } val scannedQrCode by remember { mutableStateOf<QrCode?>(null) } val previewViewSize by remember { mutableStateOf<Size?>(null) } val screenSize = with(LocalConfiguration.current) { Size(screenWidthDp.dp.toPx(), screenHeightDp.dp.toPx()) } Box(modifier = Modifier.fillMaxSize()) { // 相机预览层 AndroidView( factory = { PreviewView(it).apply { controller = cameraController layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) // 监听预览视图尺寸变化,用于坐标转换 addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> previewViewSize = Size(width.toFloat(), height.toFloat()) } } }, modifier = Modifier.fillMaxSize() ) // 聚焦指示方块(仅扫描到二维码时显示) scannedQrCode?.let { qrCode -> FocusIndicator( qrCode = qrCode, previewSize = previewViewSize, screenSize = screenSize ) } } // 二维码扫描分析逻辑 LaunchedEffect(cameraController) { val analyzer = QrCodeAnalyzer { qrResult -> scannedQrCode = qrResult } cameraController.setImageAnalysisAnalyzer( ContextCompat.getMainExecutor(context), analyzer ) } }
2. 自定义聚焦指示Composable
@Composable fun FocusIndicator(qrCode: QrCode, previewSize: Size?, screenSize: Size) { if (previewSize == null) return val qrBoundingBox = qrCode.boundingBox ?: return // 计算相机预览与屏幕的缩放比例,适配预览的居中显示逻辑 val scaleX = screenSize.width / previewSize.width val scaleY = screenSize.height / previewSize.height val fitScale = min(scaleX, scaleY) // 计算预览在屏幕上的偏移量(解决黑边/裁剪导致的坐标偏移) val offsetX = (screenSize.width - previewSize.width * fitScale) / 2f val offsetY = (screenSize.height - previewSize.height * fitScale) / 2f // 转换二维码相机坐标到UI坐标 val uiLeft = qrBoundingBox.left * fitScale + offsetX val uiTop = qrBoundingBox.top * fitScale + offsetY val uiWidth = (qrBoundingBox.right - qrBoundingBox.left) * fitScale val uiHeight = (qrBoundingBox.bottom - qrBoundingBox.top) * fitScale Canvas(modifier = Modifier.fillMaxSize()) { // 绘制红色边框(半透明,避免遮挡预览) drawRect( color = Color.Red.copy(alpha = 0.6f), topLeft = Offset(uiLeft, uiTop), size = Size(uiWidth, uiHeight), style = Stroke(width = 3.dp.toPx()) ) // 可选:添加内部半透明填充 // drawRect( // color = Color.Red.copy(alpha = 0.2f), // topLeft = Offset(uiLeft, uiTop), // size = Size(uiWidth, uiHeight) // ) } }
3. 基础QR扫描分析器示例
class QrCodeAnalyzer(private val onQrScanned: (QrCode) -> Unit) : ImageAnalysis.Analyzer { @SuppressLint("UnsafeOptInUsageError") override fun analyze(imageProxy: ImageProxy) { val mediaImage = imageProxy.image ?: run { imageProxy.close() return } val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) val options = FirebaseVisionImageDetectorOptions.Builder() .setDetectorId("QR_CODE_DETECTOR") .build() val detector = FirebaseVision.getInstance().getVisionBarcodeDetector(options) detector.detectInImage(image) .addOnSuccessListener { barcodes -> barcodes.firstOrNull { it.format == Barcode.FORMAT_QR_CODE }?.let { onQrScanned(it) } } .addOnCompleteListener { imageProxy.close() } } }
常见问题修复
- 编译错误:检查是否正确导入
QrCode(或Barcode)类、CameraX相关依赖,确保PreviewView的初始化逻辑正确 - 方块位置偏移:必须处理预览尺寸与屏幕尺寸的缩放比例,以及预览居中的偏移量,否则会出现坐标错位
- 方块不显示:确认
scannedQrCode状态已正确赋值,且qrCode.boundingBox不为空,同时Canvas的Modifier是fillMaxSize()
内容的提问来源于stack exchange,提问作者Yaakoub-H
相关产品推荐
相关产品推荐

