游乐游手机版
首页/业界动态/文章详情

Android同时扫描多个二维码的实现原理与技巧

时间:2026-08-14 19:04
超市扫码太慢?试试用Android黑科技实现“整排秒扫” 还在超市收银台前,看着收银员一个一个地扫描商品条形码吗?想象一下,如果你的手机应用能像科幻电影里那样,“唰”地一下瞬间识别视野内的所有二维码,那体验该有多酷。今天,我们就来聊聊如何借助Google官方推出的两大神器——ML Kit和Camer

超市扫码太慢?试试用Android黑科技实现“整排秒扫”

还在超市收银台前,看着收银员一个一个地扫描商品条形码吗?想象一下,如果你的手机应用能像科幻电影里那样,“唰”地一下瞬间识别视野内的所有二维码,那体验该有多酷。今天,我们就来聊聊如何借助Google官方推出的两大神器——ML Kit和CameraX,为你的Android应用赋予这种“整排秒扫”的超能力。别担心技术门槛,跟着步骤走,你也能轻松搞定。

准备工作:装备你的开发工具包

工欲善其事,必先利其器。首先,在你的模块级build.gradle文件中,引入必要的依赖库。

// build.gradle 添加这些“魔法材料”
dependencies {
    implementation 'androidx.camera:camera-camera2:1.3.1'       // 相机核心
    implementation 'androidx.camera:camera-lifecycle:1.3.1'      // 生命周期管家
    implementation 'androidx.camera:camera-view:1.3.1'  // 取景器
    implementation 'com.google.mlkit:barcode-scanning:17.1.0'   // 二维码识别引擎
}

同时,别忘了在AndroidManifest.xml中声明相机权限和硬件特性,这是应用调用摄像头的基础。


这就好比哈利·波特需要一根合适的魔杖,上述配置就是启动我们“扫描魔法”的必备装备。当然,在真机运行时,记得先在系统设置中为应用开启相机权限。

搭建舞台:创建相机预览界面

用户需要看到一个实时取景窗口。在布局文件中,添加一个全屏的PreviewView。

这个PreviewView就是我们的“魔法镜”,用户的实时取景画面将在这里流畅呈现。

核心魔法:构建二维码识别器

接下来是重头戏:编写一个图像分析器,让它能理解相机捕捉到的画面并找出其中的二维码。

class QRCodeAnalyzer(private val onDetect: (List) -> Unit) : ImageAnalysis.Analyzer {
    // 设置只识别二维码(避免误认条形码)
    private val options = BarcodeScannerOptions.Builder()
        .setBarcodeFormats(Barcode.FORMAT_QR_CODE)
        .build()
    private val scanner = BarcodeScanning.getClient(options)  // 创建识别器实例

    @SuppressLint("UnsafeExperimentalUsageError")
    override fun analyze(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image
        mediaImage?.let {
            // 将相机画面转为可识别格式
            val image = InputImage.fromMediaImage(it, imageProxy.imageInfo.rotationDegrees)
            scanImage(image, imageProxy)  // 开始扫描!
        }
    }

    private fun scanImage(image: InputImage, imageProxy: ImageProxy) {
        scanner.process(image)
            .addOnSuccessListener { codes ->
                onDetect(codes)  // 成功抓到所有二维码!
            }
            .addOnCompleteListener {
                imageProxy.close()  // 关闭当前帧,准备下一帧
            }
    }
}

这段代码就像训练了一只嗅觉灵敏的“二维码猎犬”:analyze()方法负责将相机捕获的原始图像转换成识别引擎能理解的格式;scanImage()方法则释放这只“猎犬”,让它去图像中搜寻目标;识别完成后,无论成功与否,都会自动清理当前帧,为下一次“狩猎”做好准备。

启动魔法:把一切组装起来

现在,我们需要在Activity中将相机、预览和分析器这三个核心部件组装并启动。

class MainActivity : AppCompatActivity() {
    private val cameraExecutor = Executors.newSingleThreadExecutor()  // 专用工作线程
    private val viewFinder by lazy { findViewById(R.id.viewFinder) }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()

            // 创建预览画面
            val preview = Preview.Builder().build().apply {
                setSurfaceProvider(viewFinder.surfaceProvider)
            }

            // 创建二维码识别管道
            val qrAnalyzer = ImageAnalysis.Builder().build().apply {
                setAnalyzer(cameraExecutor, QRCodeAnalyzer { codes ->
                    // 识别结果回调区 ▼
                    codes.forEachIndexed { index, code ->
                        Log.d("QR_DEBUG", "抓到二维码 ${index + 1}: ${code.rawValue}")
                    }
                })
            }

            try {
                // 组装所有部件!启动!
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(
                    this,
                    CameraSelector.DEFAULT_BACK_CAMERA,  // 使用后置摄像头
                    preview,
                    qrAnalyzer
                )
            } catch (e: Exception) {
                Log.e("CAMERA", "启动失败", e)
            }
        }, ContextCompat.getMainExecutor(this))
    }
}

启动流程可以概括为四步:首先,获取相机系统的控制权;其次,配置好实时预览界面;然后,接入我们刚刚编写的二维码识别分析器;最后,将三者绑定并启动。一个高效的批量扫码引擎就此诞生。

图片图片

图片图片


让扫描结果跃然屏上

识别出二维码只是第一步,如何优雅地将结果实时展示给用户同样重要。这其实并不复杂。

首先,设计一个用于显示结果的布局容器,通常可以放在屏幕底部。



    

    

设计思路是动态管理:每当识别到新的二维码时,就在resultsLayout中动态添加对应的视图。

在MainActivity中,添加处理扫描结果并更新UI的逻辑:

// 获取布局元素引用
private val resultsLayout by lazy { findViewById(R.id.resultsLayout) }
private val resultsContainer by lazy { findViewById(R.id.resultsContainer) }

// 显示扫描结果的核心方法
private fun showScanResults(codes: List) {
    // 清空之前的结果
    resultsLayout.removeAllViews()

    if (codes.isEmpty()) {
        // 没有扫描到结果时显示提示
        val emptyView = TextView(this).apply {
            text = "? 正在寻找二维码..."
            setTextColor(Color.WHITE)
        }
        resultsLayout.addView(emptyView)
        return
    }

    // 动态添加每个二维码结果
    codes.forEachIndexed { index, barcode ->
        val resultView = createResultView(barcode, index)
        resultsLayout.addView(resultView)
    }
}

// 创建单个结果视图
private fun createResultView(barcode: Barcode, index: Int): TextView {
    return TextView(this).apply {
        // 解析二维码内容
        val content = barcode.rawValue ?: "未知内容"

        // 格式化显示文本
        text = "✅ 二维码 ${index + 1}:\n${content.take(50)}${if (content.length > 50) "..." else ""}"
        setTextColor(Color.WHITE)
        setTypeface(null, Typeface.BOLD)
        textSize = 14f

        // 添加点击事件查看完整内容
        setOnClickListener {
            AlertDialog.Builder(this@MainActivity)
                .setTitle("二维码详情")
                .setMessage(content)
                .setPositiveButton("复制") { _, _ ->
                    copyToClipboard(content)
                    Toast.makeText(this@MainActivity, "已复制到剪贴板", Toast.LENGTH_SHORT).show()
                }
                .setNegativeButton("关闭", null)
                .show()
        }
    }
}

// 复制到剪贴板工具方法
private fun copyToClipboard(text: String) {
    val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    val clip = ClipData.newPlainText("二维码内容", text)
    clipboard.setPrimaryClip(clip)
}

图片图片

超实用小技巧

掌握了基础功能后,以下几个小技巧能让你的扫码应用体验更上一层楼:

• 性能优化:在构建ImageAnalysis时,可以设置背压策略。在ImageAnalysis.Builder()后加上.setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST),这能确保只处理最新的相机帧,有效避免因处理不及时导致的界面卡顿。

• 多类型支持:默认配置只识别二维码。如果想同时识别条形码,只需修改BarcodeScannerOptions中的.setBarcodeFormats()参数,传入如Barcode.FORMAT_QR_CODE, Barcode.FORMAT_CODE_128等多种格式即可。

• 点击聚焦:为提升识别精度,可以添加点击对焦功能。通过为viewFinder设置触摸监听器,实现点击屏幕任意位置进行对焦和测光。

// 添加点击聚焦功能
viewFinder.setOnTouchListener { _, event ->
    if (event.action == MotionEvent.ACTION_DOWN) {
        val factory = viewFinder.meteringPointFactory
        val point = factory.createPoint(event.x, event.y)
        CameraControl?.startFocusAndMetering(FocusMeteringAction.Builder(point).build())
    }
    true
}

总结一下,这套方案的架构非常清晰:CameraX负责搭建稳定高效的相机预览舞台,ML Kit提供强大精准的识别能力,再配合异步处理保证流畅度,实现多码扫描就是这么简单。

至此,你的应用已经拥有了识别整排二维码的“火眼金睛”。不妨动手试试,感受一下批量扫描带来的效率提升吧。

完整项目源码可以参考:https://github.com/Reathin/Sample-Android/tree/master/module_mlkit_barcode_scanning

来源:https://www.51cto.com/article/821484.html
上一篇原字节机器人团队负责人孔涛加盟小米消息引关注 下一篇摩尔线程上半年营收增长147% 净亏损大幅收窄
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
台式机加装固态硬盘怎么选?三星9100 PRO深度解析
业界动态 · 2026-09-01

台式机加装固态硬盘怎么选?三星9100 PRO深度解析

台式机升级存储常受限于系统启动慢、游戏加载卡顿与大文件传输延迟。本文基于三星9100 PRO的PCIe 5 0架构、14800MB s读取、13400MB s写入、2200K 2600K IOPS、1TB~8TB容量、第八代V-NAND与5nm主控、镍涂层散热与DTG技术、散热片版适配及魔术师软件,提供选购判断与安装兼容性要点,帮助读者评估是否值得一步到位升级。

宁德时代2026年中期分红61.8亿元,同比增35%,创历史新高
业界动态 · 2026-09-01

宁德时代2026年中期分红61.8亿元,同比增35%,创历史新高

宁德时代发布2026年中期分红方案,总额达61 8亿元,同比增长35%。本文梳理分红具体安排、历史对比、业绩支撑及分红机制,帮助投资者评估公司现金流实力与股东回报策略。

企业硬盘报废销毁合规指南:如何选择专业机构与处理流程
业界动态 · 2026-08-31

企业硬盘报废销毁合规指南:如何选择专业机构与处理流程

企业硬盘报废面临数据复原与合规风险,需选择具备资质且流程透明的专业机构。本文解析行业乱象,介绍以团体标准为核心的合规销毁流程,涵盖上门收运、消磁粉碎、视频溯源及尾料处置,帮助企业规避泄密责任,确保数据安全闭环。

机密文件销毁找什么机构?认准团标参编与资质合规
业界动态 · 2026-08-31

机密文件销毁找什么机构?认准团标参编与资质合规

机密文件销毁找什么机构?核心在于甄别服务商是否具备正规保密资质及是否参与行业标准制定。本文解析《商业秘密及敏感信息载体销毁通用规范》团标要求,提供筛选销毁机构的实操指南,帮助企业规避数据泄露风险,确保销毁流程合规可溯。

影石Insta360 X6全球首销登顶:8K全景画质与AI创作功能解析
业界动态 · 2026-08-31

影石Insta360 X6全球首销登顶:8K全景画质与AI创作功能解析

影石Insta360 X6全球同步发售即登顶国内外主流平台销量榜首。本文解析其搭载的索尼定制方形大底传感器、4nm AI三芯架构及8K50fps画质,详解3D时光舱、AI导演等独家功能,探讨全景相机从专业工具向大众智能创作设备的演进趋势。