Flutter で QRコードを扱う - ちょっと応用編
Android と iOS アプリを同時に開発できる Flutter です.
前回、基本的な QRコード表示をしましたが、今回は少し変更して、画像のチェックサム表示にしてみようと思います。
前回のものは、画面に決まったURLを指している QRコードを出すだけでした。
今回は画像のチェックサム (SHA256) を表示させてみようと思います。 単純な表示ではつまらないので、Flutterのコンポーネントも少し使いながら実装することにします。
ソースコードの方は以下に置いてありますので、参考にしてください。
前準備として、pubspec.yaml
に画像のアセット登録と、SHA256を計算するためのパッケージを追加します。
dependencies:
...
crypto: ^3.0.1
flutter:
...
assets:
- images/TECHaas_logo.png
画面の方を追加するので、画面遷移のために Navigator を設定します。 Navigator は、複数画面がある場合に、画面の遷移やスタック (積み重ね) を制御してくれるものです。
設定としては、MaterialApp
のroutes:
にルート名 (URLみたいなもの) とその時に表示する Widget を登録します。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
...
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => MyHomePage(title: 'Flutter QRCode'),
'/checksum': (BuildContext context) => QRCodeImagePage(title: 'SHA256 digest'),
},
);
}
}
あとは、遷移する時にはNavigator.push
して、戻る時にはNavigator.pop
するだけです。
端末の戻るボタンはpop
と同じ動作になって、普通に画面遷移するアプリになります。
遷移の方法ですが、前回作った画面で左にスワイプすると次画面に行くみたいにしてみます。
画面上の操作を検知するのには、GestureDetector
を使います。
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return GestureDetector(
onHorizontalDragEnd: (DragEndDetails details) {
if (details.primaryVelocity! > 1.0) {}
if (details.primaryVelocity! < -1.0) {
Navigator.pushNamed(context, '/checksum');
}
},
// onTap: () => {Navigator.pushNamed(context, '/checksum')},
onHorizontalDragEnd
は、横方向のスワイプ動作が終わった時に起きるイベントです。
スワイプ開始時や途中でのイベント検出も可能です。
primaryVelocity
の値は、動いた速度になります。
定義としてはin logical pixels per second
なので、1秒あたりになんピクセル動いたかで、
プラス方向は左から右へのスワイプ (X軸方向の値が大きくなる)、マイナスは逆です。
今回は次のページを模しているので、左から右へのスワイプ時に進む方向にします。
Navigator.pushNamed
は、クラスメソッド (static) です。
実装は、Navigator.of(context)
になっていますので、今のcontext
に設定されたNavigatorに対しての処理になります。
この辺最初はちょっと理解しづらいかもしれませんが、Flutter/Dart なら実装のソースコードも参照できます。
ライブラリの中でどんなことをやっているのか、少しずつ見てみると理解が深まると思います。
(vscodeを使っているなら、Goto defintion
でソースコードにアクセスできます。
また Flutter は、まだ開発途中ということもあり、あまり複雑な処理はないので、ライブラリのソースも比較的読みやすいです)
コメントになっていますが、onTap
を定義して、タップしたら遷移する処理にも変更できます。
チェックサム表示画面の方は、上の方から順に見ていきます。
qr_code_image.dart
になります。
class _QRCodeImagePageState extends State<QRCodeImagePage> {
bool _showActionButton = true;
Image? _image;
String? _checksum;
まず、ステートの変数です。
StatefulWidget
では、setState()
が呼び出されると、UIが再構築されます。
ここでは、そのUIを制御するための変数を定義しておきます。
続いて、初期化処理のinitState
です。これは widget の表示時に一度だけ呼び出されます。
@override
initState() {
super.initState();
rootBundle.load('images/TECHaas_logo.png').then((imageData) {
final Uint8List data = imageData.buffer.asUint8List();
final Digest digest = sha256.convert(data);
debugPrint('digest: $digest');
setState(() {
_image = Image.memory(data);
_checksum = digest.toString();
});
});
}
rootBundle.load()
は、アセットファイルの読み込みです。
アセットというのはアプリに埋め込んだ画像やデータで、最初に見たpubspec.yaml
で定義してあります。
アセットは無闇に多くすると、アプリのサイズが大きくなります。
ユーザのダウンロードサイズも大きくなりますので、あまり大きなファイルやデータは避けた方が良いです。
また、公開アプリに限りますが、アセットの変更でも再度審査が必要になったりしますので、なにを含めるかはよく考えましょう。
rootBundle.load()
は、Future<ByteData>
を返します。
非同期で実行されて、読み込みが終わったら、ByteData を引数としてthen()
が実行されます。
ByteData のチェックサムをとるために、Uint8List
に変換して、sha256.convert()
でダイジェストを求めます。
出来上がったものは、setState()
でステートの変数に格納します。
また、読み込んだ画像は、Image.memory
で表示用の画像に変換しています。
ここでは、ローカルアセットの読み込みなので、ほぼ一瞬で終わりますが、ネットワークなどの時間のかかる処理でも同じ様なパターンを使います。
それでは実際のUIの構築です。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Stack(
children: [
Center(
child: Container(
color: Colors.white,
child: _image,
)),
まず、Scaffold
は画面の枠組みで、タイトルとかフローティングボタンを使う時に使います。
中身は、body:
で設定します。
今回は、Stack
で、children:
に指定したウィジェットの配列を順々に重ねて描画します。
まず、画像を画面中央に表示します。
次の部分が、チェックサムをQRコードのしたものを表示している部分です。
if (!_showActionButton && _checksum != null) ...[
Align(
alignment: Alignment.bottomRight,
child: GestureDetector(
onTap: () => {
setState(() => {_showActionButton = true})
},
child: Container(
padding: EdgeInsets.all(5),
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 2.0),
),
child: QrImage(
size: 200,
data: _checksum!,
version: QrVersions.auto,
),
))),
),
],
],
),
if
文のあと、...[ ]
はスプレッド演算子と呼ばれていて、Collection
(List
, Set
, Map
等)で使うと、要素を追加することができます。
ここでは、上で見たStack
のchildren:
を指定する子ウィジェットの配列に、if
がtrue
の場合、Align
が追加されます。
Align
は、画面のどこかに子要素を寄せる場合に使います。
buttomRight
指定なので、child:
要素が右下寄せになります。
QrImage
がQRコードの実体です。
その周りにBoxDecoration
で枠線を引きます。
padding
指定はそれぞれ余白を追加する感じです。
残りは、フローティングボタンになります。画面の右下の方に出てくるボタンです。
floatingActionButton: _showActionButton
? FloatingActionButton(
onPressed: () => {setState(() => _showActionButton = false)},
tooltip: 'Show Code',
child: const Icon(Icons.qr_code),
)
: null,
);
QRコード表示のアイコンを置いたボタンで、押されたら、_showActionButton
を切り替えます。
また、_showActionButton
は三項演算で、true
なら、FloatingActionButton
が、false
ならnull
になります。
これで、_showActionButton
によって非表示切り替えができるようになります。
最初は表示状態ですが、ボタンをタップすると、QRコードのイメージに切り替わり、そこでタップすると、またボタンに変わるという処理をしています。

以上、少し応用を効かせて、単純な表示からボタンでの表示切り替えをするにしてみました。
次回は、QRコードの認識側を作ってみようと思います。