B-Teck!

お仕事からゲームまで幅広く

【Android/Kotlin】ZXing Android Embeddedを用いて簡単にQRコードを扱う

Androidアプリの勉強のため、QRコードリーダーの開発を行っています。
とりあえずQRコードを読み取る・生成する部分については実装することができました。
せっかくなのでご紹介しようと思います。

記事内に登場するコードは、下記リポジトリにて記載されているものが殆どです。
動作しているものを確認したい場合はご覧ください。 github.com

また、本記事は下記のQiita記事を参考にKotlinで実装を行ったもの+αとなります。 QRコードの読取、生成をする [Android] - Qiita

もくじ

1. ライブラリ導入

github.com

app/build.gradle に下記の記述を追加して、ライブラリの導入を行います。
元記事ではminSdkVersionを15にしていましたが、最新のライブラリを利用するために24にしました。

android {
    compileSdkVersion 29
    defaultConfig {
        minSdkVersion 24
        targetSdkVersion 29
    }
}

執筆時点で最新のリリースバージョンは4.0.2なので、そちらを利用しています。

dependencies {
    ...
    implementation 'com.journeyapps:zxing-android-embedded:4.0.2'
}

2. QRコード読み込み

QRコード読み取り画面を起動

下記の処理を呼び出すことで、 QRコード読み取り画面を起動できます。

IntentIntegrator(this).initiateScan()

IntentIntegratorは、定義されているメソッドを通して各種設定値を変更することができます。
READMEに記載されているカスタマイズ例をKotlinで書くと下記のようになると思います。

IntentIntegrator(this).apply {
    desiredBarcodeFormats(IntentIntegrator.ONE_D_CODE_TYPES)
    prompt("Scan a barcode")
    cameraId(0) // Use a specific camera of the device
    beepEnabled(false)
    barcodeImageEnabled(true)
}.initiateScan()

その他のオプションや詳細については、下記のソースを確認してください。
zxing-android-embedded/IntentIntegrator.java at master · journeyapps/zxing-android-embedded · GitHub

読み取った結果の取得・表示

IntentIntegratorで起動したActivityから結果を受け取ります。
result.contentsで読み取った結果を文字列で取り出すことができます。
下記のコードでは読み取った文字列をトーストで表示しています。

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
    when {
        result != null -> Toast.makeText(this, result.contents, Toast.LENGTH_LONG).show()
        else -> super.onActivityResult(requestCode, resultCode, data)
    }
}

3.文字列からQRコード生成

事前準備

変換対象の文字列入力用EditTextと、結果表示用ImageViewを配置します。
(コードなし)

文字列をQRコードに変換して画面上にセットする

BarcodeEncoder().encodeBitmap()を用いて、文字列をQRコードに変換します。
sizeの500は適当な数値なので、用途に応じて変更してしまって大丈夫です。
EncodeHintType のMapを渡さずにマルチバイト文字をQRコードにすると、decode時に化けてしまうので注意。

private fun makeQRCode(contents: String) {
    val size = 500
    try {
        //QRコードをBitmapで作成(UTF-8を指定)
        val bitmap = BarcodeEncoder().encodeBitmap(
            contents,
            BarcodeFormat.QR_CODE,
            size,
            size,
            mapOf(EncodeHintType.CHARACTER_SET to "UTF-8")
        )
        //作成したQRコードを画面上に配置
        imageView.setImageBitmap(bitmap)
    } catch (e: WriterException) {
        throw AndroidRuntimeException("Barcode Error.", e)
    }
}

4.その他実装してみた機能

URLとして解釈できたらブラウザで起動

android.webkit.URLUtil.isValidUrl() で、文字列がURLとして解釈可能か判定できます。

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        val result = IntentIntegrator
            .parseActivityResult(requestCode, resultCode, data)
            ?.contents

        when {
            result.isNullOrEmpty() -> super.onActivityResult(requestCode, resultCode, data)
            URLUtil.isValidUrl(result) -> dialogAction(
                result,
                "読み取りURLをブラウザで開きますか?",
                "開く",
                ::openBrowser
            )
            else -> dialogAction(
                result,
                "読み取りテキストをクリップボードにコピーしますか?",
                "コピー",
                ::copyToClipBoard
            )
        }
    }

URLとして解釈できる場合に、URL型を渡して Intent.ACTION_VIEW を起動してやることで、
デフォルトに設定されているブラウザが立ち上がり、読み取ったURLを表示することができました。

private fun openBrowser(contents: String) =
    startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(contents)))

テキストをクリップボードにコピー

android.content.ClipboardManager を用いることで実現できます。
ClipData.newPlainText("", contents) の第一引数にはラベルを設定できるようですが、
ユーザーからはあまり参照されない部分なので、とりあえず空文字で埋めてます。

private fun copyToClipBoard(contents: String) {
    val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    val clip = ClipData.newPlainText("", contents)
    clipboard.setPrimaryClip(clip)
    Toast.makeText(this, "クリップボードにコピーしました", Toast.LENGTH_SHORT).show()
}

他のアプリからテキスト共有でインテント起動

他のアプリからのIntent起動を許容するため、下記の記述を追加します。 * app/src/main/AndroidManifest.xml

<intent-filter>
    <action android:name="android.intent.action.SEND" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/*" />
</intent-filter>

その上で、渡ってきたintentに対して下記のように記述してやると値を取り出すことができます。
intent.dataString で取れるよ、みたいなのを見たんですが、どうにもうまく取れなかったので、
実際は intent.getStringExtra(Intent.EXTRA_TEXT) にて取り出しています。

val intentString = intent.dataString ?: intent.getStringExtra(Intent.EXTRA_TEXT)

取り出した文字列をEditTextにセットして、QRコード生成処理を呼び出すことで、
他のアプリから共有された文字列からQRコード生成を行うような実装ができました。

// 外部から起動されて文字列が渡ってきていたらQR生成
val intentString = intent.dataString ?: intent.getStringExtra(Intent.EXTRA_TEXT)
if (!intentString.isNullOrEmpty()) {
    editText.setText(intentString)
    makeQRCode(editText.text.toString())
}