超市扫码太慢?试试用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
