Beta ComputingのAndroid担当のd-ariakeです。
Android案件でバーコードを扱う機会があったので、実装方法を残したいと思います。
行うこと
今回は以下の2点を共有したいと思います。
- ZXing Android Embedded を使い、QRコードとJANコードを生成する。
- Glide のカスタムModel Loaderで生成・キャッシュさせる。
ZXing Android Embeddedによるバーコードの生成
ZXing Android Embedded は Zxing をAndroid向けに使いやすくしたライブラリです。
このライブラリを使うと以下のようなコードを書くだけで、簡単にバーコードを生成することができます。
// QRコードを生成するコード val hints = mapOf( EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.H, EncodeHintType.CHARACTER_SET to "Shift_JIS", EncodeHintType.QR_VERSION to 12 ) val bitmap: android.graphics.Bitmap = BarcodeEncoder().encodeBitmap(contents, BarcodeFormat.QR, width, height, hints)
// JAN (EAN) コードを生成するコード val bitmap: android.graphics.Bitmap = BarcodeEncoder().encodeBitmap(contents, BarcodeFormat.EAN_13, width, height)
本当に簡単に生成できちゃいます!
余談:
JANコードを生成する際には12桁か13桁の数値文字列を渡す必要があります。
13桁目はチェックサムとなっていて、誤り検出に使われるみたいです。
EAN13Writer.java と UPCEANReader.java のソースを読んだ感じ、12桁のコードを渡すと自動的にチェックサムを計算して13桁として生成してくれるようです。
GlideのカスタムModel Loaderの実装
Androidで画像を扱う際には何かしらの画像管理ライブラリを使うかと思います。
今回は私が一番慣れているGlideを使用して、QRコード・JANコードを生成・キャッシュし、ImageView
に表示させるといったことを行いました。
ネット上の画像の場合はそのままURL文字列を渡してしまえばすぐに実現できてしまいます。
Glide.with(this.applicationContext) .load("https://画像のリンク.png") .placeholder(R.drawable.プレースホルダー) .into(this.imageView)
しかし、今回のようにQR・JANコードを生成する場合は自分で ModelLoader
を実装する必要があります。
以下に実装手順を載せていきます。
(公式ドキュメント: Glide v4 : Writing a custom ModelLoader)
まず、以下のようにライブラリを追加します。
(必要に応じて apply plugin: 'kotlin-kapt'
を追記してください。)
// https://mvnrepository.com/artifact/com.journeyapps/zxing-android-embedded implementation("com.journeyapps:zxing-android-embedded:4.1.0") // https://mvnrepository.com/artifact/com.github.bumptech.glide/glide implementation("com.github.bumptech.glide:glide:4.11.0") // https://mvnrepository.com/artifact/com.github.bumptech.glide/compiler kapt("com.github.bumptech.glide:compiler:4.11.0")
次に、Glideの Model
に相当する BarcodeRequest
を定義します。
sealed class
で定義し、Qr
型と Jan
型で表現しました。
package jp.co.betacomputing.barcodeapp import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel sealed class BarcodeRequest { abstract val content: String data class Qr(override val content: String, val level: ErrorCorrectionLevel) : BarcodeRequest() data class Jan(override val content: String) : BarcodeRequest() }
次に ModelLoader<Model (= BarcodeRequest,) Data (= Bitmap)>
と DataFetcher<Data (= Bitmap)>
の実装です。
BarcodeRequest
を width
と height
とともに Fetcher
に渡し、そこで Bitmap
を生成しています。
今回は特にネット経由で何かをダウンロードするといったことはないので、getDataSource()
で DataSource.LOCAL
を指定しており、リソースの解放処理も不要なので clearnup()
では何もしていません。
(公式のドキュメントのBase64の例でも同じような感じでしたね。)
loadData()
で実際のバーコードの生成処理を行います。
オプションを指定して BarcodeEncoder().encodeBitmap()
を叩くだけです。
生成に成功したら DataCallback#onDataReady()
を叩いて完了を通知します。
package jp.co.betacomputing.barcodeapp import android.graphics.Bitmap import com.bumptech.glide.Priority import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.Options import com.bumptech.glide.load.data.DataFetcher import com.bumptech.glide.load.model.ModelLoader import com.bumptech.glide.signature.ObjectKey import com.google.zxing.BarcodeFormat import com.google.zxing.EncodeHintType import com.journeyapps.barcodescanner.BarcodeEncoder import jp.co.betacomputing.barcodeapp.BarcodeRequest as Req internal class BarcodeModelLoader : ModelLoader<Req, Bitmap> { override fun buildLoadData(model: Req, width: Int, height: Int, options: Options): ModelLoader.LoadData<Bitmap>? = ModelLoader.LoadData(ObjectKey(model), Fetcher(model, width, height)) override fun handles(model: Req): Boolean = true private class Fetcher( private val request: Req, private val width: Int, private val height: Int ) : DataFetcher<Bitmap> { override fun getDataClass(): Class<Bitmap> = Bitmap::class.java override fun cleanup() = Unit override fun getDataSource(): DataSource = DataSource.LOCAL override fun cancel() = Unit override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Bitmap>) { when (this.request) { is Req.Qr -> this.generateQr(this.request, callback) is Req.Jan -> this.generateJan(this.request, callback) } } private fun generateQr(request: Req.Qr, callback: DataFetcher.DataCallback<in Bitmap>) { try { val hints = mapOf(EncodeHintType.ERROR_CORRECTION to request.level) val bitmap = BarcodeEncoder() .encodeBitmap(this.request.content, BarcodeFormat.QR_CODE, this.width, this.height, hints) callback.onDataReady(bitmap) } catch (e: Exception) { callback.onLoadFailed(e) } } private fun generateJan(request: Req.Jan, callback: DataFetcher.DataCallback<in Bitmap>) { try { val bitmap = BarcodeEncoder() .encodeBitmap(request.content, BarcodeFormat.EAN_13, this.width, this.height) callback.onDataReady(bitmap) } catch (e: Exception) { callback.onLoadFailed(e) } } } }
そして、この BarcodeModelLoader
をGlideに登録する部分です。
これはほとんどドキュメント通りです。
package jp.co.betacomputing.barcodeapp import android.content.Context import android.graphics.Bitmap import com.bumptech.glide.Glide import com.bumptech.glide.Registry import com.bumptech.glide.annotation.GlideModule import com.bumptech.glide.load.model.ModelLoader import com.bumptech.glide.load.model.ModelLoaderFactory import com.bumptech.glide.load.model.MultiModelLoaderFactory import com.bumptech.glide.module.AppGlideModule @GlideModule internal class BarcodeGlideModule : AppGlideModule() { override fun registerComponents(context: Context, glide: Glide, registry: Registry) { registry.prepend(BarcodeRequest::class.java, Bitmap::class.java, Factory()) } private class Factory : ModelLoaderFactory<BarcodeRequest, Bitmap> { override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<BarcodeRequest, Bitmap> = BarcodeModelLoader() override fun teardown() = Unit } }
最後に、この BarcodeModelLoader
を実際に使った簡単なアプリを作りました。
ボタンを押すと、このModel Loader経由でバーコードが生成され、ImageView
に設定されます。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" tools:context=".MainActivity"> <ImageView android:id="@+id/qrImageView" android:layout_width="256dp" android:layout_height="256dp" android:layout_margin="10dp" android:background="#ef5350" android:contentDescription="@null" /> <ImageView android:id="@+id/janImageView" android:layout_width="256dp" android:layout_height="102dp" android:layout_margin="10dp" android:background="#42a5f5" android:contentDescription="@null" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" android:text="PUSH ME!" tools:ignore="HardcodedText" /> </LinearLayout>
package jp.co.betacomputing.barcodeapp import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.bumptech.glide.Glide import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) this.setContentView(R.layout.activity_main) this.button.setOnClickListener { // QRコード Glide.with(this) .load(BarcodeRequest.Qr(content = "あいうえお", level = ErrorCorrectionLevel.H)) .into(this.qrImageView) // JANコード Glide.with(this) .load(BarcodeRequest.Jan(content = "020000100001")) .into(this.janImageView) } } }
以上、GlideのカスタムModel Loaderを作って、バーコードを表示させる方法でした。
Glideのキャッシュやロード機能を使いたいけれども、画像の取得部分は自分で実装したいというときは是非やってみてください。