AndroidでQRコードを生成して画面に表示する
zxingでQRコード生成
Created at Updated at

1978 Words
⚠️

AndroidアプリでQRコードを生成して画面に表示する方法。今回は読み取りについては書いていない。

追記 (2020-09-02)

あまりにあんまりだったのでサンプルコードを修正した。
まぁ多少はマシになった。

追記 (2020-07-21)

データバインディングの始め方についての記事へのリンクを追加。

追記 (2020-03-31)

lifecycleに関する依存先のバージョンを2.2.0にアップデート。
それに伴い、ViewModelProviderを使用したViewModelのインスタンス生成方法を修正。

ViewModelProviders.of(owner)ViewModelProvider(owner)]


準備

ここでは(無駄に)データバインディングを使用しているので、準備が必要ならしておく。

Android - DataBindingはじめ - すいはんぶろぐ.io

build.gradle (app)

journeyapps/zxing-android-embedded というライブラリを使うことにした。

dependencies {
...
    // QR code
    implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
}

READMEに色々書いてあるが、QRコードの読み込みはしないで単純にQRコードを生成するだけならそんなに色々やる必要はなさそう。

Model

package com.suihan74.sample

import android.graphics.Color
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel

/** QRコード生成の元情報 */
data class QRSource(
    /** QR化するデータ */
    val data: String,

    /**
     * QRコードサイズ(px)
     *
     * dpではなくpxなので注意
     */
    val size: Int,

    /** 誤り訂正レベル */
    val errorCorrectionLevel: ErrorCorrectionLevel,

    /** 文字コード */
    val charset: String,

    /**
     * マージン(セル数, 4セル以上の余白が必要)
     *
     * レイアウト側で確実に4セル分以上の余白を用意してある場合には0にした方が制御しやすいかもしれない
     */
    val margin: Int = 4,

    /** 前景色 */
    val foregroundColor: Int = Color.BLACK,

    /** 背景色 */
    val backgroundColor: Int = Color.WHITE
)

ViewModel

package com.suihan74.sample

import android.graphics.Bitmap
import android.graphics.Color
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.journeyapps.barcodescanner.BarcodeEncoder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class HogeViewModel : ViewModel() {
    /** QRコード化する情報 */
    var qrSource: QRSource? = null
        private set

    /** QRコードのビットマップ */
    val qrBitmap: LiveData<Bitmap?> by lazy {
        MutableLiveData<Bitmap?>(null)
    }

    /** QRコード化する情報をセット */
    fun setQRSource(qrSource: QRSource?) {
        this.qrSource = qrSource
        viewModelScope.launch(Dispatchers.Default) {
            (qrBitmap as MutableLiveData<Bitmap?>).postValue(
                generateQRCodeBitmap(qrSource)
            )
        }
    }

    /** QRコードのビットマップを生成 */
    private fun generateQRCodeBitmap(qrSource: QRSource?) : Bitmap? =
        if (qrSource == null) null
        else try {
            // 生成に関するパラメータ
            val hints = mapOf(
                // マージン
                EncodeHintType.MARGIN to qrSource.margin,
                // 誤り訂正レベル
                EncodeHintType.ERROR_CORRECTION to qrSource.errorCorrectionLevel,
                // 文字コード
                EncodeHintType.CHARACTER_SET to qrSource.charset
            )

            BarcodeEncoder().encodeBitmap(
                qrSource.data,
                BarcodeFormat.QR_CODE,
                pxSize, pxSize,
                hints
            ).also { encoder ->
                // 黒白以外にしたいならここで適当にQRコードを色付けする
                val pixels = IntArray(pxSize * pxSize)
                encoder.getPixels(pixels, 0, pxSize, 0, 0, pxSize, pxSize)
                for (idx in pixels.indices) {
                    pixels[idx] =
                        if (pixels[idx] == Color.BLACK) qrSource.foregroundColor
                        else qrSource.backgroundColor
                }
                encoder.setPixels(pixels, 0, pxSize, 0, 0, pxSize, pxSize)
            }
        }
        catch (e: Throwable) {
            Log.e("genQRCode", Log.getStackTraceString(e))
            null
        }
}

Activity

package com.suihan74.sample

import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProviders
import com.suihan74.sample.R
import com.suihan74.sample.databinding.ActivityHogeBinding
import kotlinx.android.synthetic.main.activity_hoge.*

class HogeActivity : AppCompatActivity {
    private val viewModel: HogeViewModel by lazy {
        ViewModelProvider(this)[HogeViewModel::class.java]
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        DataBindingUtil.setContentView<ActivityHogeBinding>(
            this,
            R.layout.activity_hoge
        ).apply {
            lifecycleOwner = this@HogeActivity
            vm = viewModel
        }

        // サンプルだし適当にこの辺で値入れちゃう
        viewModel.setQRSource(
            QRSource(
                data = "https://suihan742.github.io",
                size = resources.getDimensionPixelSize(R.dimen.qr_size),
                errorCorrectionLevel = ErrorCorrectionLevel.M,
                charset = "UTF-8",
                margin = 0,
            ),
            this
        )
    }
}

BindingAdapter

package com.suihan74.sample

import android.graphics.Bitmap
import android.widget.ImageView
import androidx.databinding.BindingAdapter

/** ImageViewにBitmapをバインドする */
@BindingAdapter("bitmap")
fun ImageView.setBitmapSource(bitmap: Bitmap?) {
    this.setImageBitmap(bitmap)
}

Layout

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="vm"
            type="com.suihan74.sample.HogeViewModel" />
    </data>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/QRImageView"
            bitmap="@{vm.qrBitmap}"
            android:contentDescription="@null"
            android:scaleType="center"
            android:layout_width="@dimen/qr_size"
            android:layout_height="@dimen/qr_size" />

...

    </LinearLayout>
</layout>

Bitmapの生成はコルーチンで行い、生成完了したらpostValue()で渡す。

EncodeHintType

barcodeEncoder.encodeBitmap()hintsを渡すことで色々指定できる。

指定できる項目は以下。QRコード以外の二次元コードに関する設定値についての説明は適当(というかよく知らないのでコメント直訳)

  • ERROR_CORRECTION …… 誤り訂正レベルの指定。

    • QRコードのエンコーディングではErrorCorrectionLevel.L,M,Q,Hのどれかを指定できる。
      訂正率をより高くすれば汚れや破損に強くなるが、誤り訂正に必要な情報量が増える。バージョンを指定する場合、エンコード対象のデータ量と誤り訂正に必要なデータ量を考慮する必要がある。
    ErrorCorrectionLevel誤り訂正率(%)
    L7
    M15
    Q25
    H30
    • Aztecコードの場合は1~99のパーセンテージを整数値で与えるらしい

    • PDF417コードの場合は0~8の整数値を与えるらしい

  • CHARACTER_SET …… dataの文字コード (デフォルト: “ISO-8859-1”)
    URLをエンコードしたい場合はデフォルトでとくに問題にならないように思えるが、日本語文字列などをエンコードしたい場合は指定する必要がある。

  • MARGIN …… マージンサイズの指定 (デフォルト: 4)
    QRコードは周囲4セル以上のマージンが必要らしいが、レイアウト側で別途マージンを指定している場合には0にしておいた方が扱いやすいように思える。

  • QR_VERSION …… QRコードのバージョン (デフォルト: データサイズと訂正率に応じて変化?)
    1~40で指定できる。バージョンごとにセル数が決まっており、バージョンが大きいほどよりセル数が多くなる(表現できる情報量が増える)
    参考: https://www.qrcode.com/about/version.html

  • DATA_MATRIX_SHAPE …… 正方形か長方形かということを指定するらしい
    通常のQRコードの場合とくに指定しないかFORCE_SQUAREでいい

    • SynbolShapeHint.FORCE_NONE …… 指定なし
    • SynbolShapeHint.FORCE_SQUARE …… 正方形
    • SynbolShapeHint.FORCE_RECTANGLE …… 長方形

以下QRコード以外に対するもの

  • PDF417_COMPACT …… PDF417コードでコンパクトモードを使用するか (true or false)

  • PDF417_COMPACTION …… PDF417コードでのコンパクト化する際のモード

  • PDF417_DIMENSIONS …… 行数・列数の最大最小値(Dimensions型)

  • AZTEC_LAYERS …… Aztecコードのレイヤー数

    • (-1,-2,-3,-4) …… コンパクトAztecコード
    • 0 …… 最小レイヤー数になるようにする (デフォルト)
    • (1,2,…,32) …… 通常の(非コンパクトな)Aztecコード
  • GS1_FORMAT …… GS1標準のデータエンコードを強制するか (true or false)
    GS1QRコードについてはこの辺? https://www.dsri.jp/standard/2d-symbol/gs1-qr.html

See Also