You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 15:43:14