Andorid基于ZXing實(shí)現(xiàn)二維碼生成&掃描

最近遇上了掃描條碼的需求,在查找資料過(guò)程中不是那么地順利,做個(gè)筆記,記錄下這兩篇文章,前人栽樹(shù)后人乘涼。
Andorid基于ZXing實(shí)現(xiàn)二維碼生成&掃描
Android基于MLKit實(shí)現(xiàn)條形碼掃碼
本篇文章Demo下載

ZXing介紹

說(shuō)到二維碼,大量的資料都會(huì)提到ZXing,具體見(jiàn)ZXing,這是一個(gè)用Java語(yǔ)言實(shí)現(xiàn)的1D/2D 條形碼圖像處理庫(kù)。涉及專業(yè)知識(shí)不多做介紹,這篇文章只講使用。

二維碼生成

引入ZXing核心庫(kù):

implementation 'com.google.zxing:core:3.5.1'

創(chuàng)建二維碼位圖,寫了一個(gè)工具類,可以直接使用:

object QrCodeUtil {
    /**
     * 創(chuàng)建二維碼位圖 (支持自定義配置和自定義樣式)
     * @param content 字符串內(nèi)容
     * @param width 位圖寬度,要求>=0(單位:px)
     * @param height 位圖高度,要求>=0(單位:px)
     * @param character_set 字符集/字符轉(zhuǎn)碼格式 (支持格式:{@link CharacterSetECI })。傳null時(shí),zxing源碼默認(rèn)使用 "ISO-8859-1"
     * @param error_correction 容錯(cuò)級(jí)別 (支持級(jí)別:{@link ErrorCorrectionLevel })。傳null時(shí),zxing源碼默認(rèn)使用 "L"
     * @param margin 空白邊距 (可修改,要求:整型且>=0), 傳null時(shí),zxing源碼默認(rèn)使用"4"。
     * @param color_black 黑色色塊的自定義顏色值
     * @param color_white 白色色塊的自定義顏色值
     * @return
     */
    fun createQRCodeBitmap(
        content: String,
        width: Int,
        height: Int,
        character_set: String = "UTF-8",
        error_correction: String = "H",
        margin: String = "1",
        @ColorInt color_black: Int = Color.BLACK,
        @ColorInt color_white: Int = Color.WHITE,
    ): Bitmap? {
        /** 1.參數(shù)合法性判斷  */
        if (width < 0 || height < 0) { // 寬和高都需要>=0
            return null
        }
        try {
            /** 2.設(shè)置二維碼相關(guān)配置,生成BitMatrix(位矩陣)對(duì)象  */
            val hints: Hashtable<EncodeHintType, String> = Hashtable()
            if (character_set.isNotEmpty()) {
                hints[EncodeHintType.CHARACTER_SET] = character_set // 字符轉(zhuǎn)碼格式設(shè)置
            }
            if (error_correction.isNotEmpty()) {
                hints[EncodeHintType.ERROR_CORRECTION] = error_correction // 容錯(cuò)級(jí)別設(shè)置
            }
            if (margin.isNotEmpty()) {
                hints[EncodeHintType.MARGIN] = margin // 空白邊距設(shè)置
            }
            val bitMatrix =
                QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints)

            /** 3.創(chuàng)建像素?cái)?shù)組,并根據(jù)BitMatrix(位矩陣)對(duì)象為數(shù)組元素賦顏色值  */
            val pixels = IntArray(width * height)
            for (y in 0 until height) {
                for (x in 0 until width) {
                    if (bitMatrix[x, y]) {
                        pixels[y * width + x] = color_black // 黑色色塊像素設(shè)置
                    } else {
                        pixels[y * width + x] = color_white // 白色色塊像素設(shè)置
                    }
                }
            }
            /** 4.創(chuàng)建Bitmap對(duì)象,根據(jù)像素?cái)?shù)組設(shè)置Bitmap每個(gè)像素點(diǎn)的顏色值,之后返回Bitmap對(duì)象  */
            val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
            bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
            return bitmap
        } catch (e: WriterException) {
            e.printStackTrace()
        }
        return null
    }
}

使用到了ZXing核心功能的就是這句
QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints),
傳入內(nèi)容、樣式、寬高以及配置。

在配置EncodeHintType.CHARACTER_SET字符格式時(shí),我們使用了"UTF-8",這是為了兼容中文,ZXing源碼默認(rèn)使用的是"ISO-8859-1",而"ISO-8859-1"本身是不支持中文的。

看一下使用效果:

val imageView = findViewById<ImageView>(R.id.iv)
val bitmap = createQRCodeBitmap("你好,初次見(jiàn)面,請(qǐng)多指教!", 480, 480)
imageView.setImageBitmap(bitmap)
生成二維碼

生成帶logo小圖的二維碼

很多時(shí)候見(jiàn)到的二維碼中間都會(huì)帶有一個(gè)logo小圖,我們也來(lái)實(shí)現(xiàn)一下這樣子的效果。

其實(shí)就是把兩個(gè)bitmap繪制在一塊,在原有的方法上補(bǔ)充即可,添加兩個(gè)參數(shù):

object QrCodeUtil {
    /**
     * 創(chuàng)建二維碼位圖 (支持自定義配置和自定義樣式)
     * @param content 字符串內(nèi)容
     * @param width 位圖寬度,要求>=0(單位:px)
     * @param height 位圖高度,要求>=0(單位:px)
     * @param character_set 字符集/字符轉(zhuǎn)碼格式 (支持格式:{@link CharacterSetECI })。傳null時(shí),zxing源碼默認(rèn)使用 "ISO-8859-1"
     * @param error_correction 容錯(cuò)級(jí)別 (支持級(jí)別:{@link ErrorCorrectionLevel })。傳null時(shí),zxing源碼默認(rèn)使用 "L"
     * @param margin 空白邊距 (可修改,要求:整型且>=0), 傳null時(shí),zxing源碼默認(rèn)使用"4"。
     * @param color_black 黑色色塊的自定義顏色值
     * @param color_white 白色色塊的自定義顏色值
     * @param logoBitmap logo小圖片
     * @param logoPercent logo小圖片在二維碼圖片中的占比大小,范圍[0F,1F],超出范圍->默認(rèn)使用0.2F。
     * @return
     */
    fun createQRCodeBitmap(
        content: String,
        width: Int,
        height: Int,
        character_set: String = "UTF-8",
        error_correction: String = "H",
        margin: String = "1",
        @ColorInt color_black: Int = Color.BLACK,
        @ColorInt color_white: Int = Color.WHITE,
        logoBitmap: Bitmap? = null,
        logoPercent: Float = 0f
    ): Bitmap? {
        /** 1.參數(shù)合法性判斷  */
        if (width < 0 || height < 0) { // 寬和高都需要>=0
            return null
        }
        try {
            /** 2.設(shè)置二維碼相關(guān)配置,生成BitMatrix(位矩陣)對(duì)象  */
            val hints: Hashtable<EncodeHintType, String> = Hashtable()
            if (character_set.isNotEmpty()) {
                hints[EncodeHintType.CHARACTER_SET] = character_set // 字符轉(zhuǎn)碼格式設(shè)置
            }
            if (error_correction.isNotEmpty()) {
                hints[EncodeHintType.ERROR_CORRECTION] = error_correction // 容錯(cuò)級(jí)別設(shè)置
            }
            if (margin.isNotEmpty()) {
                hints[EncodeHintType.MARGIN] = margin // 空白邊距設(shè)置
            }
            val bitMatrix =
                QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints)

            /** 3.創(chuàng)建像素?cái)?shù)組,并根據(jù)BitMatrix(位矩陣)對(duì)象為數(shù)組元素賦顏色值  */
            val pixels = IntArray(width * height)
            for (y in 0 until height) {
                for (x in 0 until width) {
                    if (bitMatrix[x, y]) {
                        pixels[y * width + x] = color_black // 黑色色塊像素設(shè)置
                    } else {
                        pixels[y * width + x] = color_white // 白色色塊像素設(shè)置
                    }
                }
            }
            /** 4.創(chuàng)建Bitmap對(duì)象,根據(jù)像素?cái)?shù)組設(shè)置Bitmap每個(gè)像素點(diǎn)的顏色值,之后返回Bitmap對(duì)象  */
            val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
            bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
            /** 5.為二維碼添加logo小圖標(biāo) */
            if (logoBitmap != null) {
                return addLogo(bitmap, logoBitmap, logoPercent)
            }
            return bitmap
        } catch (e: WriterException) {
            e.printStackTrace()
        }
        return null
    }

    private fun addLogo(srcBitmap: Bitmap?, logoBitmap: Bitmap?, logoPercent: Float): Bitmap? {
        /** 1.參數(shù)合法性判斷  */
        if (srcBitmap == null || logoBitmap == null) {
            return null
        }
        var percent = logoPercent
        if (logoPercent < 0F || logoPercent > 1F) {
            percent = 0.2F
        }
        /** 2. 獲取原圖片和Logo圖片各自的寬、高值 */
        val srcWidth = srcBitmap.width
        val srcHeight = srcBitmap.height
        val logoWidth = logoBitmap.width
        val logoHeight = logoBitmap.height

        /** 3. 計(jì)算畫布縮放的寬高比 */
        val scaleWidth = srcWidth * percent / logoWidth
        val scaleHeight = srcHeight * percent / logoHeight

        /** 4. 使用Canvas繪制,合成圖片 */
        val bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        canvas.drawBitmap(srcBitmap, 0f, 0f, null)
        canvas.scale(scaleWidth, scaleHeight, (srcWidth / 2).toFloat(), (srcHeight / 2).toFloat())
        canvas.drawBitmap(logoBitmap, srcWidth * 1f / 2 - logoWidth / 2, srcHeight * 1f / 2 - logoHeight / 2, null)
        return bitmap
    }
}

看一下使用效果:

        val imageViewLogo = findViewById<ImageView>(R.id.iv_logo)
        val logo = BitmapFactory.decodeResource(resources, R.drawable.cat)
        val bitmapLogo = createQRCodeBitmap(
            content = "你好,初次見(jiàn)面,請(qǐng)多指教!",
            width = 480,
            height = 480,
            logoBitmap = logo,
            logoPercent = 0.3f
        )
        imageViewLogo.setImageBitmap(bitmapLogo)
帶logo二維碼

二維碼掃描

借助開(kāi)源庫(kù) ZXing Android Embedded 實(shí)現(xiàn)二維碼掃描。

ZXing Android Embedded 是用于Android的條形碼掃描庫(kù),使用ZXing進(jìn)行解碼。

更多的使用可以下載源碼工程跑下樣例查看,包括設(shè)置前后攝像頭、設(shè)置掃描超時(shí)時(shí)間等,該篇文章就只介紹最基本的二維碼掃描使用。

引入庫(kù):

implementation 'com.journeyapps:zxing-android-embedded:4.3.0'

使用相機(jī)掃描二維碼

跳轉(zhuǎn)到掃描頁(yè)面后會(huì)自動(dòng)開(kāi)始掃描,掃描到結(jié)果后會(huì)將結(jié)果返回,onActivityResult廢棄之后,使用Activity Result API獲取頁(yè)面回傳數(shù)據(jù),不了解Activity Result API的可以查看這篇文章Android Activity Result API使用

    private val barcodeLauncher = registerForActivityResult(
        ScanContract()
    ) { result: ScanIntentResult ->
        if (result.contents == null) {
            val originalIntent = result.originalIntent
            if (originalIntent == null) {
                Toast.makeText(this@MainActivity, "Cancelled", Toast.LENGTH_LONG).show()
            } else if (originalIntent.hasExtra(Intents.Scan.MISSING_CAMERA_PERMISSION)) {
                Toast.makeText(this@MainActivity, "Cancelled due to missing camera permission", Toast.LENGTH_LONG)
                    .show()
            }
        } else {
            Toast.makeText(this@MainActivity, "Scanned: " + result.contents, Toast.LENGTH_LONG).show()
        }
    }
        findViewById<Button>(R.id.bt).setOnClickListener {
            barcodeLauncher.launch(ScanOptions())
        }

這是使用默認(rèn)的掃描頁(yè)面,使用方法很簡(jiǎn)單,但是更多的情況下,我們都需要自定義掃描頁(yè)面樣式。

自定義CustomScannerActivity,啟動(dòng)掃描時(shí)設(shè)置CaptureActivity即可:

        findViewById<Button>(R.id.bt2).setOnClickListener {
            val options = ScanOptions().setOrientationLocked(false).setCaptureActivity(
                CustomScannerActivity::class.java
            )
            barcodeLauncher.launch(options)
        }

具體的在自定義掃描頁(yè)面中如何配置掃描view,可以參考本篇文章Demo下載或者ZXing Android Embedded 源碼工程,里面有樣例可以參考,本篇文章也是參考的里面的樣例。

從相冊(cè)中識(shí)別二維碼圖片

在樣例中并沒(méi)有找到從相冊(cè)中識(shí)別二維碼圖片的方法,最終在issue中發(fā)現(xiàn)有提到同樣的問(wèn)題以及解答。

打開(kāi)相冊(cè)獲取圖片:

    private fun openGallery() {
        val intent = Intent()
        intent.type = "image/*"
        intent.action = Intent.ACTION_GET_CONTENT
        openGalleryRequest.launch(Intent.createChooser(intent, "識(shí)別相冊(cè)二維碼圖片"))
    }
    private val openGalleryRequest =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
            if (it.resultCode == RESULT_OK) {
                it.data?.data?.let { uri -> handleImage(uri) }
            }
        }

解析圖片二維碼:

    private fun handleImage(uri: Uri) {
        try {
            val image = MediaStore.Images.Media.getBitmap(this.contentResolver, uri)

            val intArray = IntArray(image.width * image.height)
            image.getPixels(intArray, 0, image.width, 0, 0, image.width, image.height)

            val source = RGBLuminanceSource(image.width, image.height, intArray)
            val reader = MixedDecoder(MultiFormatReader())
            var result = reader.decode(source)
            if (result == null) {
                result = reader.decode(source)
            }
            Toast.makeText(this@MainActivity, "Scanned: ${result?.text}", Toast.LENGTH_LONG).show()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

上述方法從相冊(cè)中識(shí)別二維碼圖片,發(fā)現(xiàn)存在識(shí)別失敗的問(wèn)題,尤其是商品條形碼,使用相機(jī)掃描商品條形碼是可以正常掃描識(shí)別出來(lái)的,但是將商品條形碼拍照保存進(jìn)相冊(cè),使用從相冊(cè)中識(shí)別二維碼圖片方法,卻出現(xiàn)識(shí)別失敗的情況。

為此,又去查找了其他的資料,見(jiàn)下一篇文章Android基于MLKit實(shí)現(xiàn)條形碼掃碼

參考文檔:
http://www.itdecent.cn/p/b275e818de6a
http://www.itdecent.cn/p/c75f16de1b2c
http://www.itdecent.cn/p/b85812b6f7c1
https://github.com/journeyapps/zxing-android-embedded/discussions/685

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容