Android Tips #42 ZXing ライブラリ (2.1) を使って QR コードを生成する

catch

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

前回のエントリに引き続き、今回は QR コードの生成を試してみたいと思います!
Button を 押すと EditText に入力された文字列を Bitmap に変換し ImageView に表示するサンプルを作ります。

zxing_generate02

ZXing の概要とライブラリの導入について知りたいかたは前回のエントリを参考にしてください。

Activity の作成

まずは Activity をサクッと作ります。Bitmap の生成は非同期で行いたいので AsyncTaskLoader を呼べるよう FragmentActivity を継承しておいてください。

activity_generate.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="6dp"
    >
    <EditText 
        android:id="@+id/edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_alignParentTop="true"
        android:hint="QRコードにしたい文字を入力"
        />
    <Button 
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_below="@+id/edit_text"
        android:text="Generate"
        />
    <ImageView 
        android:id="@+id/result_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/button"
        />
</RelativeLayout>

GenerateActivity.java

package jp.classmethod.android.sample.barcodescanner;

import android.support.v4.app.FragmentActivity;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;

public class GenerateActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_generate);
        findViewById(R.id.button).setOnClickListener(onClickListener);
    }
    
    private OnClickListener onClickListener = new OnClickListener() {
        @Override
        public void onClick(View view) {
            // EditText から文字を取得
            EditText editText = (EditText) findViewById(R.id.edit_text);
            String contents = editText.getText().toString();
            
            // TODO QR コードを生成して ImageView に表示
        }
    };
}

下図のようなレイアウトになると思います。

zxing_generate01

ZXing で String を QR コードに変換する

次に OnClickListener のメソッド内で取得した String を QR コードに変換していきます。まずQRCodeWriter というクラスを使って String をエンコードします。エンコードした結果は BitMatrix という型で渡されます。

QRCodeWriter writer = new QRCodeWriter();
BitMatrix bm = writer.encode(contents, BarcodeFormat.QR_CODE, 100, 100);

第一引数に String を渡し、第二引数で出力フォーマットを指定します。今回は QR コードを作りたいので BarcodeFormat.QR_CODE とします。第三引数・第四引数は幅と高さです。今回は適当に小さめにしていますが、もっと高精細にしたい場合は端末の画面サイズから計算すると良いでしょう。まぁ QR コードに高精細も何もないと思いますが。w
次に Bitmap を作っていきます。BitMatrix#get() メソッドを呼ぶと、バーコードのなかの対象の座標を塗りつぶす場合は true, 塗りつぶさない場合は false を返してくれます。これを使って 100 x 100 のすべてのピクセルを検証してデータを作ります。

int width = bm.getWidth();
int height = bm.getHeight();
int[] pixels = new int[width * height];
// データがあるところだけ黒にする
for (int y = 0; y < height; y++) {
    int offset = y * width;
    for (int x = 0; x < width; x++) {
        pixels[offset + x] = bm.get(x, y) ? Color.BLACK : Color.WHITE;
    }
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);

以上で終わりです!めちゃ簡単ですね!
あとはこの処理を AsyncTaskLoader につめこんで完成です。すべて実装すると以下のようになります。

package jp.classmethod.android.sample.barcodescanner;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;

public class GenerateActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_generate);
        findViewById(R.id.button).setOnClickListener(onClickListener);
    }
    
    private OnClickListener onClickListener = new OnClickListener() {
        @Override
        public void onClick(View view) {
            // EditText から文字を取得
            EditText editText = (EditText) findViewById(R.id.edit_text);
            String contents = editText.getText().toString();
            // 非同期でエンコードする
            Bundle bundle = new Bundle();
            bundle.putString("contents", contents);
            getSupportLoaderManager().initLoader(0, bundle, callbacks);
        }
    };
    
    private LoaderCallbacks<Bitmap> callbacks = new LoaderCallbacks<Bitmap>() {
        @Override
        public Loader<Bitmap> onCreateLoader(int id, Bundle bundle) {
            EncodeTaskLoader loader = new EncodeTaskLoader(
                    getApplicationContext(), bundle.getString("contents"));
            loader.forceLoad();
            return loader;
        }
        @Override
        public void onLoaderReset(Loader<Bitmap> loader) {
        }
        @Override
        public void onLoadFinished(Loader<Bitmap> loader, Bitmap bitmap) {
            getSupportLoaderManager().destroyLoader(0);
            if (bitmap == null) {
                // エンコード失敗
                Toast.makeText(getApplicationContext(), "Error.", Toast.LENGTH_SHORT).show();
            } else {
                // エンコード成功
                ImageView imageView = (ImageView) findViewById(R.id.result_view);
                imageView.setImageBitmap(bitmap);
            }
        }
    };
    
    public static class EncodeTaskLoader extends AsyncTaskLoader<Bitmap> {
        private String mContents;
        public EncodeTaskLoader(Context context, String contents) {
            super(context);
            mContents = contents;
        }
        @Override
        public Bitmap loadInBackground() {
            try {
                // エンコード結果を返す
                return encode(mContents);
            } catch (Exception e) {
                // 何らかのエラーが発生したとき
                return null;
            }
        }
        private Bitmap encode(String contents) throws Exception {
            QRCodeWriter writer = new QRCodeWriter();
            // エンコード
            BitMatrix bm = null;
            bm = writer.encode(mContents, BarcodeFormat.QR_CODE, 100, 100);
            // ピクセルを作る
            int width = bm.getWidth();
            int height = bm.getHeight();
            int[] pixels = new int[width * height];
            // データがあるところだけ黒にする
            for (int y = 0; y < height; y++) {
                int offset = y * width;
                for (int x = 0; x < width; x++) {
                    pixels[offset + x] = bm.get(x, y) ? Color.BLACK : Color.WHITE;
                }
            }
            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
            return bitmap;
        }
    }
}

実際に動かしてみるとこんな感じです。

zxing_generate02

前回作った読み取りアプリで読み取ってみましょう!入力したデータがちゃんと読み取れると思います。

zxing_generate03

また上記のピクセルを作る処理で色を変えると、好きな色のバーコードなんてのも作れます。

zxing_generate04

まとめ

2回のエントリにわたり QR コードの読み取り・生成についてみていきました。読み取りも書き込みもとってもシンプルに実装できて良いですね!優れたライブラリは実装を楽にしてくれますね。とはいうものの今回触れたところは導入部分のみです。ZXing は他にもいろいろな種類のバーコードの生成ができたり、画像から読み取ったりといろいろなことができるので奥が深いです。ぜひとも使いこなして、素敵なアプリを作っていきましょう!

参考