KotlinでQRコード読み取り機能を実装【Android】


## はじめに
今回は`zxing-android-embedded`を使ってQRコードの読み取り機能を実装します。


読み取り画面のレイアウトは、デフォルトのものを使うこともできますが、今回は色々とカスタマイズできるよう自作のレイアウトを適用するように実装します。以下は完成画面です。

<img width="400" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1175390/ab3a07dc-16da-8de2-aeaa-de209d32ff6d.png">



## 実装

### ライブラリの導入

`build.gradle`のdependenciesに以下のように記述します。


```kotlin:build.gradle(:app)
dependencies {
  implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
  
//省略
}
```

`AndroidManifest.xml`には以下を追加します。



```kotlin:AndroidManifest.xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FLASHLIGHT" />

<application
        android:hardwareAccelerated="true" //ハードウェアアクセラレーションを有効にする
       
//省略       
        <activity
            android:name=".QRCodeCaptureActivity"
            android:exported="false" />
```

これで準備完了です。



### TOP画面

次にアプリを起動した際に初期表示されるトップ画面を作成します。ボタンを押すと、QR読み取り画面が開きます。

<img width="400" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1175390/a45aa591-7b39-2d38-cc8c-bf1ceedd5137.png">



```kotlin:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/qr_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="QRカメラ起動"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />


</androidx.constraintlayout.widget.ConstraintLayout>
```





次にボタンを押した際にQRカメラを起動するよう、clickListenerと読み取った結果をトーストで表示する実処理を書いていきます。

今回は自作のレイアウトを使用するため、`QRCodeCaptureActivity`を起動するようにします。


```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var qrButton = findViewById<Button>(R.id.qr_button)


        //ボタンを押した際QR読み取り画面に遷移する
        qrButton.setOnClickListener{
            IntentIntegrator(this).apply{
                captureActivity=QRCodeCaptureActivity::class.java
            }.initiateScan()
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        //読み取った結果の受け取り
        val result = IntentIntegrator.parseActivityResult(resultCode,data)
        if(result.contents != null){
            //読み取った結果をトーストで表示する
            Toast.makeText(this, result.contents, Toast.LENGTH_LONG).show()
        }
    }
}
```


### QR読み取り画面
読み取り画面のレイアウトを作成します。

```kotlin:fragment_qrcode.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:zxing_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <com.journeyapps.barcodescanner.CompoundBarcodeView
        android:id="@+id/qrcode_reader"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0"
        zxing_view:zxing_scanner_layout="@layout/qr_code_reader"></com.journeyapps.barcodescanner.CompoundBarcodeView>

    <TextView
        android:id="@+id/qrTextView"
    //省略    
    />
</androidx.constraintlayout.widget.ConstraintLayout>
```


```kotlin:qr_code_reader.xml
<?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:zxing_barcode_view="http://schemas.android.com/apk/res-auto"
        xmlns:zxing_finder="http://schemas.android.com/apk/res-auto"
       >

        <com.journeyapps.barcodescanner.BarcodeView
            android:id="@+id/zxing_barcode_surface"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            zxing_barcode_view:zxing_framing_rect_height="300dp"
            zxing_barcode_view:zxing_framing_rect_width="300dp" />

        <View
            android:layout_width="305dp"
            android:layout_height="305dp"
            android:background="@drawable/qr_code_frame"
            android:layout_gravity="center"></View>

        <com.journeyapps.barcodescanner.ViewfinderView
            android:id="@+id/zxing_viewfinder_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            zxing_finder:zxing_possible_result_points="@color/zxing_transparent"
            zxing_finder:zxing_result_view="@color/zxing_result_view"
            zxing_finder:zxing_viewfinder_laser="@color/zxing_transparent"
            zxing_finder:zxing_viewfinder_mask="@color/zxing_viewfinder_mask" />
    </merge>
```


```kotlin:qr_code_frame.xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="220dp"
    android:height="220dp"
    android:viewportHeight="230.0"
    android:viewportWidth="230.0">
    <path
        android:pathData="M 0 0 H 230 V 230 H 0 Z"
        android:fillType="evenOdd"
        android:fillColor="#00000000"
        android:strokeWidth="10"
        android:strokeColor="@android:color/white" />
</vector>
```



最後に、QRCodeCaptureActivityを処理を書いていきます。



```kotlin:QRCodeCaptureActivity
class QRCodeCaptureActivity : CaptureActivity() {
    private lateinit var capture: CaptureManager
    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.fragment_qrcode )

        var  barcodeScannerView = findViewById<com.journeyapps.barcodescanner.CompoundBarcodeView>(R.id.qrcode_reader)
      
        // デフォルトで表示される赤い線を消す
        disableLaser(barcodeScannerView!!)

        var capture = CaptureManager(this, barcodeScannerView!!)
        capture!!.initializeFromIntent(intent, savedInstanceState)
        capture!!.decode()
    }

    override fun onResume() {
        super.onResume()
        var  barcodeView = findViewById<com.journeyapps.barcodescanner.CompoundBarcodeView>(R.id.qrcode_reader)
        barcodeView.resume()
    }

    override fun onPause() {
        super.onPause()
        var barcodeView = findViewById<com.journeyapps.barcodescanner.CompoundBarcodeView>(R.id.qrcode_reader)
        barcodeView.pause()
    }

    override fun onDestroy() {
        super.onDestroy()
        var barcodeView = findViewById<com.journeyapps.barcodescanner.CompoundBarcodeView>(R.id.qrcode_reader)
        barcodeView.pause()
    }

    // 赤い線を消す処理
    private fun disableLaser(decoratedBarcodeView: DecoratedBarcodeView) {
        val scannerAlphaField = ViewfinderView::class.java.getDeclaredField("SCANNER_ALPHA")
        scannerAlphaField.isAccessible = true
        scannerAlphaField.set(decoratedBarcodeView.viewFinder, intArrayOf(0))
    }
}
```

これで実装完了です。実際にサンプルのQRコードを読み取ってみます。

<img width="400" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1175390/ab3a07dc-16da-8de2-aeaa-de209d32ff6d.png">

<img width="400" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1175390/39c0423f-5ac5-72ab-43ae-5a865d1c754a.png">


### ※つまづいた点
ActivityのonResume()/onPause()でCompoundBarcodeViewのresume()/pause()を呼ぶことを忘れないよう注意してください。これをしないと画面を開いても真っ黒な画面にしかなりません。



## 最後に
以上になります。

デフォルトで用意されているレイアウトを使用すれば、もっと簡単に実装できますが、いかんせんできることに限りがあります。

かゆいところに手が届かないといった状況に陥らないためにも、最初から自作のレイアウトを適用しておくと、柔軟な開発ができるかと思います。

是非こちら参考にしていただけますと幸いです。


## 参考
[AndroidでQRコード読み取りアプリをライブラリを使用して作ってみた](https://phoneappli.hatenablog.com/entry/2020/12/17/161115)

[[Kotlin] Zxing QRコードリーダーをカスタマイズ](https://qiita.com/Mosea/items/e9dae626713fe9950734)