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 誤り訂正率(%) L 7 M 15 Q 25 H 30 Aztecコードの場合は1~99のパーセンテージを整数値で与えるらしい
PDF417コードの場合は0~8の整数値を与えるらしい
- QRコードのエンコーディングでは
CHARACTER_SET
…… dataの文字コード (デフォルト: “ISO-8859-1”)
URLをエンコードしたい場合はデフォルトでとくに問題にならないように思えるが、日本語文字列などをエンコードしたい場合は指定する必要がある。MARGIN
…… マージンサイズの指定 (デフォルト: 4)
QRコードは周囲4セル以上のマージンが必要らしいが、レイアウト側で別途マージンを指定している場合には0にしておいた方が扱いやすいように思える。QR_VERSION
…… QRコードのバージョン (デフォルト: データサイズと訂正率に応じて変化?)
1~40で指定できる。バージョンごとにセル数が決まっており、バージョンが大きいほどよりセル数が多くなる(表現できる情報量が増える)
参考: https://www.qrcode.com/about/version.htmlDATA_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