PsyTouSan’s LAB

アプリ開発に関することから、くだらないことまで。

【Java】Android Studio でバーコードリーダーを実装したい

この記事は、群馬高専AdventCalendar14日目の記事です。

Android Studioを使って、バーコードリーダを作成していきたいと思います。
また、作成するにあたって押さえておきたい点も以下に列挙します。

  1. カメラ画面のカスタマイズ
  2. アクセス許可を尋ねるダイアログの表示
  3. JANコードのみを認識するようにする
  4. 連続的な読み取りを可能にする

この三つです。それぞれ、順番に説明いたします。

アクセス許可のダイアログ表示

よくあるのは、「カメラのアクセスを許可しますか」とか、「マイクのアクセスを許可しますか」とか、「位置情報の利用を許可しますか」といったやつです。アプリが、スマホに実装されているUIを利用することを許可するか否かを聞いてくるダイアログはよく見かけると思います。今回はせっかくなので「カメラのアクセスを許可しますか」的なダイアログを表示させて、許可をタップしたらアプリの開始、許可をしなかったらアプリが終了する、という簡単なプログラムを組んでみたいと思います。

JANコードのみを認識させる

バーコードと一言にいっても、QRコードなどの二次元コードから、JANコードITFコードなどの様々なバーコードがありますが、これらを自動的に認識して、JANコードであればその内容を取得するというシステムにするということです。

連続的に読み取りができる

これは要するに、一回目のバーコード読み取りの後に、すぐに二回目のバーコード読み取りを可能にするということです。三回目以降も同じです。ただし、同じバーコードを連続して読み取ってもしょうがないので、最後に読み取ったバーコードと違う番号のバーコードである場合に限って、その後の処理をさせるようなプログラムに仕上げます。今回は、バーコードを読み取った場合に、トーストを表示させるようにしていきます。

カメラ画面をカスタマイズする

バーコードの読み取り画面には、当然ですがカメラを使って、その映像を映している部分があります。デフォルトの設定だと、画面いっぱいにカメラ画面が広がってしまうので、同じカメラ画面にも、カメラ映像部分を自由に配置したり、テキストを表示させたり、ボタンの配置をしたりなど、UIを加えていこうと思います。

アプリの作成

使用するライブラリ

今回の開発に使用するライブラリは、ZXingというライブラリです。これで、「ゼブラクロッシング」と読むそうです。
まずは、このライブラリをインストールするところから始まります。以下に、ZXingのライブラリの元を置いておくので、興味のある方はご覧になってください。
github.com
それでは、実際にインストールしていきます。以下に示したコードをbuild.gradle(:app)に記入します。dependenciesはすでに書かれていると思うので、中括弧内を記入するようにしてください。

dependencies {
    implementation 'com.journeyapps:zxing-android-embedded:3.0.2@aar'
    implementation 'com.google.zxing:core:3.2.0'
}

記入したら、「Gradleファイルが変更されましたよ」的なメッセージとともに、右上付近に「Sync now」が出てくると思うので、それをクリックしてください。
これでインストールは完了です。簡単にできてしまいますね。続いて、UIの配置をしていきます。

レイアウトの作成

今回は、起動したらバーコード読み取り画面が開き、バーコード読み取ることでテキストボックスに読み取った内容を表示するような設計にしたいと思います。以下が、そのソースコードになります。

<?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">

    <com.journeyapps.barcodescanner.CompoundBarcodeView
        android:id="@+id/barcodeView"
        android:layout_width="340dp"
        android:layout_height="160dp"
        android:layout_marginTop="40dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">
    </com.journeyapps.barcodescanner.CompoundBarcodeView>

    <TextView
        android:id="@+id/getNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="60dp"
        android:text="Hello World!"
        android:textSize="30sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/barcodeView" />

</androidx.constraintlayout.widget.ConstraintLayout>

このソースコードを入力した際の、ブループリントが以下のスクショです。

カメラ画面のブループリント

見てもらうと分かる通り、バーコードリーダのカメラ画面は、com.journeyapps.barcodescanner.CompoundBarcodeView で配置できるようになります。あとは、layout_width や layout_height などで、サイズを決めたり、android:id などでidを付けたりします。この辺りは、他のオブジェクトと変わりありません。
続いて、Javaを用いてプログラムを書いていきます。

Javaプログラムの作成

とりあえず、ソースコードを張り付けておきます。

package com.samplegame.barcode_sample;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Camera;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.ResultPoint;
import com.journeyapps.barcodescanner.BarcodeCallback;
import com.journeyapps.barcodescanner.BarcodeResult;
import com.journeyapps.barcodescanner.CompoundBarcodeView;
import com.journeyapps.barcodescanner.camera.CameraSettings;

import java.util.List;

public class MainActivity extends AppCompatActivity {
    CompoundBarcodeView barcodeView;
    private String lastResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){
            String[] permissions = {Manifest.permission.CAMERA};
            ActivityCompat.requestPermissions(MainActivity.this, permissions, 100);
            return;
        }
        CameraSetting();
        readBarcode();
    }

    private void CameraSetting(){
        barcodeView = findViewById(R.id.barcodeView);
        CameraSettings settings = barcodeView.getBarcodeView().getCameraSettings();
        barcodeView.getBarcodeView().setCameraSettings(settings);
        barcodeView.setStatusText("バーコードが読めます");
        barcodeView.resume();
        readBarcode();
    }
    private void readBarcode(){
        barcodeView.decodeContinuous(new BarcodeCallback() {
            final TextView getNumber = findViewById(R.id.getNumber);
            @Override
            public void barcodeResult(BarcodeResult result) {
                //このif文で、不必要な連続読みを防ぐ
                if (result.getText() == null || result.getText().equals(lastResult)){
                    return;
                }
                //このif文で、読み取られたバーコードがJANコードかどうか判定する
                if (result.getBarcodeFormat() != BarcodeFormat.EAN_13){
                    return;
                }
                lastResult = result.getText();
                Toast.makeText(MainActivity.this, "読み取りました", Toast.LENGTH_LONG).show();
                getNumber.setText(result.getText());
            }

            @Override
            public void possibleResultPoints(List<ResultPoint> resultPoints) {

            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 100 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){
                return;
            }
        }
        CameraSetting();
    }
}

ネストif文があったり、一行が長い部分が存在したりと、綺麗なコードとは言えませんが、ざっとこんな感じです。

カメラ画面のカスタマイズ

よくある方法としては、IntentIntegratorクラスのinitiateScanメソッドを使用する方法があります。ところが、この方法では画面いっぱいにカメラが広がったアクティビティが立ち上がってしまいます。これでは、カメラ画面のカスタマイズという最初に掲げた目標からそれてしまうので、今回はCameraSettingクラスを使用する方法で実装しました。

アクセス許可を尋ねるダイアログの表示

まず、アプリの起動時にカメラの使用が許可されているかをチェックします。その判定を行っているのが、以下に示したコードです。

        if(ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){
            String[] permissions = {Manifest.permission.CAMERA};
            ActivityCompat.requestPermissions(MainActivity.this, permissions, 100);
            return;
        }

これをonCreate内に記述しています。もし、カメラの使用許可が下りていない場合は、ダイアログを表示します。そのダイアログの表示を担っているのが、以下のコードです。

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 100 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){
                return;
            }
        }
        CameraSetting();
    }

onRequestPermissionResultメソッドを継承しています。ここでもう一度、許可が下りているかどうか判定を行っています。こうして、ユーザーからカメラの使用を許可された場合に、CameraSettingメソッドを実行しています。もし拒否された場合は、アプリは起動したままですが、カメラには何も映りません。

アクセス許可のダイアログ
JANコードのみを認識するようにする

バーコードがJANコードなのかどうかを判定するには、BarcodeFormat という列挙クラスを使うと都合が良さそうだったので、これを使用しました。以下のスクショは、裏表紙のISBNを読み取った時のものです。

実際に読み取った様子

試しに、当ブログのURLをQRコードに変換したものを撮影してみましたが、反応していないのがわかります。

QRコードには反応しない
連続的に読み取りができる

最後に読み取ったバーコードと違う番号を認識した場合に、テキストビューに新たに読み取った方の番号を表示します。これは、decodeContinuousメソッドで実装できます。仮に、一回のみ読み取りたい場合は、decodeSingleメソッドを用いるといいでしょう。

まとめ

まとめると、

  • カメラ画面はCameraSettingクラスを用いることでカスタマイズ可能
  • アクセス許可ダイアログは、標準ライブラリであるonRequestPermissionResultメソッドを実装すれば割と簡単に作れる
  • 認識したバーコードは、BarcodeFormaから取得できる
  • 連続的な読み取りはdecodeContinuousメソッドを用いる

といった感じです。
出来るだけわかりやすくなるように解説したつもりでしたが、私自身、能力不足を感じる部分もありますので、説明が下手になっている部分もあると思いますが、参考になれば幸いです。

それではまた。