仿微信做個(gè)極速的二維碼掃描功能!
微信改了推動(dòng)機(jī)制,真愛(ài)請(qǐng)星標(biāo)本公號(hào) 公眾號(hào)回復(fù)加入BATcoder技術(shù)群 BAT
作者:安和橋北的少年
https://juejin.cn/user/616168885849976
正文
一直為網(wǎng)上找的掃碼項(xiàng)目無(wú)法快速且識(shí)別率高的掃描并解析出二維碼而苦惱,zxing自己修改集成的難度又比較高(菜是原罪)。好在,經(jīng)高人指導(dǎo),接觸到了谷歌的MLkit工程,實(shí)際體驗(yàn)下來(lái),是真的好用,在此做一個(gè)經(jīng)驗(yàn)分享,也是為了自己以后能記住。先來(lái)看看集成之后的效果圖:

跟微信掃碼比起來(lái),不能說(shuō)是一模一樣,但至少是有點(diǎn)相似了。

如此迷你的二維碼也能解析出來(lái)哦。接下來(lái),我們來(lái)看下如何具體實(shí)現(xiàn)吧。
首先,來(lái)看下重中之重,MLKit的項(xiàng)目介紹吧。
https://developers.google.cn/ml-kit
可以看到,這個(gè)項(xiàng)目除了掃碼功能,還有其他各種各樣的好用且免費(fèi)的SDK,像是文字識(shí)別,人臉識(shí)別等等,都可以依賴(lài)這個(gè)項(xiàng)目輕松搞定。無(wú)奈小弟才疏學(xué)淺,只接觸了該項(xiàng)目的掃碼功能,等來(lái)日水平提升了,定將其他的功能也一一體驗(yàn)一遍。
對(duì)了,這個(gè)項(xiàng)目除了Android端,還有IOS端的哦,感興趣的IOS小伙伴可以自行參考
要使用這個(gè)項(xiàng)目,必不可少的,需要先引入工程。
dependencies {
// ...
// Use this dependency to bundle the model with your app
implementation 'com.google.mlkit:barcode-scanning:16.1.1'
}
有g(shù)oogle play條件的小伙伴,還可以選擇添加google play的相關(guān)引用,甚至可以使用google play model。
dependencies {
// ...
// Use this dependency to use the dynamically downloaded model in Google Play Services
implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:16.1.4'
}
<application ...>
...
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="barcode" />
<!-- To use multiple models: android:value="barcode,model2,model3" -->
</application>
CameraX的支持當(dāng)然也要一并加上。
// 版本號(hào)
def camerax_version = "1.0.0-rc03"
// 對(duì)camera 及 camera2的支持,可自行選擇
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
項(xiàng)目使用的是基于jetpack lifecycle的框架,所以加上lifecycle的支持。
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
預(yù)覽用到了CameraX自帶的預(yù)覽控件。
implementation "androidx.camera:camera-view:1.0.0-alpha22"
接下來(lái)就是具體的代碼實(shí)現(xiàn)了。首先,用到了相機(jī)及相冊(cè),自然需要添加相應(yīng)的權(quán)限申請(qǐng)。
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE),REQUEST_PERMISSION)
先來(lái)看看掃碼界面的實(shí)現(xiàn),主要包含了預(yù)覽的PreviewView及用于繪制掃碼線(xiàn)條及掃碼結(jié)果的ScanOverlay。
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<com.hsmedia.mlkitdemo.ScanOverlay
android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/iv_exit"
app:layout_constraintBottom_toTopOf="@id/tv_tips"
android:layout_marginBottom="20dp"
android:layout_marginTop="20dp"
/>
接下來(lái)看一下如何開(kāi)啟CameraX的預(yù)覽。CameraX自帶了檢測(cè)相機(jī)是否可用的監(jiān)聽(tīng),可以在相機(jī)可用之后,再進(jìn)行后續(xù)操作。
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
bindScan(cameraProvider, overlay.width,overlay.height)
}, ContextCompat.getMainExecutor(this@BarcodeScanningActivity))
并且,CameraX綁定生命周期控件后,可以根據(jù)生命周期,自行釋放相機(jī),媽媽再也不用擔(dān)心忘記關(guān)相機(jī)啦。
val preview : Preview = Preview.Builder().build()
//綁定預(yù)覽
preview.setSurfaceProvider(previewView.surfaceProvider)
//使用后置相機(jī)
val cameraSelector : CameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
//將相機(jī)綁定到當(dāng)前控件的生命周期
camera = cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, imageAnalysis, preview)
這樣,就可以開(kāi)啟預(yù)覽啦,可以注意到,代碼中綁定到生命周期的時(shí)候,使用了一個(gè)imageAnalysis的useCases,而這,就是用于圖片掃描的組件了。
//配置圖片掃描
val imageAnalysis = ImageAnalysis.Builder()
.setTargetResolution(Size(width, height))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
光這樣還不行,還需要為圖片掃描配置解析掃碼內(nèi)容的解析器QRCodeAnalyser,說(shuō)了這么多,終于要用到MLKit的二維碼解析了。
來(lái)看一下QRCodeAnalyser的具體實(shí)現(xiàn)。
@SuppressLint("UnsafeExperimentalUsageError")
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image ?: kotlin.run {
imageProxy.close()
return
}
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
detector.process(image)
.addOnSuccessListener { barCodes ->
if (barCodes.size > 0){
listener.invoke(barCodes[0],imageProxy.width,imageProxy.height)
//接收到結(jié)果后,就關(guān)閉解析
detector.close()
}
}
.addOnFailureListener { Log.d(TAG, "Error: ${it.message}") }
.addOnCompleteListener { imageProxy.close() }
}
這個(gè)類(lèi)主要實(shí)現(xiàn)了ImageAnalysis.Analyzer,并實(shí)現(xiàn)了analyze解析方法,其中imageProxy就是CameraX傳遞過(guò)來(lái)的圖片掃描內(nèi)容了。其主要內(nèi)容,就是根據(jù)傳遞過(guò)來(lái)的圖片掃描內(nèi)容,使用detector進(jìn)行解析,而這個(gè)detector又是怎么來(lái)的呢?
//配置當(dāng)前掃碼格式
private val options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(
Barcode.FORMAT_QR_CODE,
Barcode.FORMAT_AZTEC
)
.build()
//獲取解析器
private val detector = BarcodeScanning.getClient(options)
這里的BarcodeScanning就是MLKit提供的二維碼解析組件了。解析完成后,對(duì)掃碼結(jié)果進(jìn)行后續(xù)操作。
//綁定圖片掃描解析
imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), QRCodeAnalyser{
barcode,imageWidth,imageHeight->
//解綁當(dāng)前所有相機(jī)操作
cameraProvider.unbindAll()
//初始化縮放比例
initScale(imageWidth,imageHeight)
barcode.boundingBox?.let {//掃描二維碼的外邊框矩形
overlay.addRect(translateRect(it))
Log.i(TAG, "bindScan: left:${it.left} right:${it.right} top:${it.top} bottom:${it.bottom}")
}
Handler().postDelayed({
//延遲1S后返回結(jié)果
val intent = Intent()
intent.putExtra(SCAN_RESULT,barcode.rawValue)
setResult(Activity.RESULT_OK, intent)
},1000)
})
這里需要注意的是,因?yàn)槭謾C(jī)上overlay的繪制區(qū)域與實(shí)際的圖片掃描區(qū)域并不是一致的,所以需要對(duì)返回內(nèi)容中二維碼的外邊框矩形進(jìn)行一個(gè)轉(zhuǎn)換,否則,最后繪制的結(jié)果點(diǎn)位置會(huì)出錯(cuò)。
首先,根據(jù)繪制區(qū)域的大小和圖片掃描區(qū)域的大小,初始化縮放比例。
private fun initScale(imageWidth : Int, imageHeight : Int){
if(isPortraitMode(this)){
scaleY = overlay.height.toFloat() / imageWidth.toFloat()
scaleX = overlay.width.toFloat() / imageHeight.toFloat()
}else{
scaleY = overlay.height.toFloat() / imageHeight.toFloat()
scaleX = overlay.width.toFloat() / imageWidth.toFloat()
}
}
這里需要注意的是,因?yàn)閷?shí)際相機(jī)和掃描的角度,在豎屏模式下,有一個(gè)90度的差別,所以這里實(shí)際上做的比例是用繪制區(qū)域的高和掃描區(qū)域的寬,繪制區(qū)域的寬和掃描區(qū)域的高去做一個(gè)計(jì)算。
private fun translateX(x: Float): Float = x * scaleX
private fun translateY(y: Float): Float = y * scaleY
//將掃描的矩形換算為當(dāng)前屏幕大小
private fun translateRect(rect: Rect) = RectF(
translateX(rect.left.toFloat()),
translateY(rect.top.toFloat()),
translateX(rect.right.toFloat()),
translateY(rect.bottom.toFloat())
)
根據(jù)比例去計(jì)算實(shí)際的二維碼外邊框矩形,并計(jì)算出最后的繪制點(diǎn),進(jìn)行繪制。
最后的掃碼效果,無(wú)論是掃碼速度,還是解析率,我覺(jué)得都要比之前用的基于zxing的掃碼工程要高,感興趣的小伙伴可以自行嘗試一下。
推薦閱讀
? 耗時(shí)2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!
? 『BATcoder』做了多年安卓還沒(méi)編譯過(guò)源碼?一個(gè)視頻帶你玩轉(zhuǎn)!
BATcoder技術(shù)群,讓一部分人先進(jìn)大廠
大家好,我是劉望舒,騰訊云最具價(jià)值專(zhuān)家TVP,著有暢銷(xiāo)書(shū)《Android進(jìn)階之光》《Android進(jìn)階解密》《Android進(jìn)階指北》,連續(xù)四年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,谷歌開(kāi)發(fā)者社區(qū)特邀講師。
前華為技術(shù)專(zhuān)家,現(xiàn)大廠技術(shù)負(fù)責(zé)人。
想要加入 BATcoder技術(shù)群,公號(hào)回復(fù)BAT 即可。
為了防止失聯(lián),歡迎關(guān)注我的小號(hào)
微信改了推送機(jī)制,真愛(ài)請(qǐng)星標(biāo)本公號(hào)??
