環境
- Flutter 1.12.16
- AndroidStudio 3.5
概要
タッチした場所に図形を描画するアプリをFlutterで作成する。完成イメージは以下のようになる。
このアプリの作成を通してGestureDetectorとCustomPaintの基本的な使い方を紹介する。
準備
アプリの作成はAndroidStudioで行った。まずは新規のFlutterプロジェクトを作成し、main.dartを次のように編集してアプリの土台を作る。
main.dart
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Pointer drawing lesson',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(),
);
}
}
描画ウィジェットの作成
それでは描画ウィジェットを作成しよう。このウィジェットが、最終的には先に上記に掲載したコードのMaterialAppのhomeで指定されているScaffoldと置き換わる。描画ウィジェットはタッチした点を覚えておく必要があるため、StatefulWidgetとして作成する。main.dartに以下のコードを追加する。
class PointerDrawingWidget extends StatefulWidget {
PointerDrawingWidget({Key key, this.title}) : super(key: key);
final String title;
@override
_PointerDrawingWidgetState createState() => _PointerDrawingWidgetState();
}
このように、状態を持つようなウィジェットを作る場合はStatefulWidget
を継承する。その実装はcreateState()
をオーバーライドしてStateクラスを返すことのみを行う。
_PointerDrawingWidgetState()
が、このWidgetのためのStateを表すクラスである。dart.mainに、続けて次のコードを実装しよう。
// 描画ウィジェットの状態クラス
class _PointerDrawingWidgetState extends State<PointerDrawingWidget> {
// タッチした点を覚えておく
final _points = List<Offset>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: GestureDetector(
// TapDownイベントを検知
onTapDown: _addPoint,
// カスタムペイント
child: CustomPaint(
painter: MyPainter(_points),
// タッチを有効にするため、childが必要
child: Center(),
),
),
floatingActionButton: FloatingActionButton(
// 点のクリアボタン
onPressed: _clearPoints,
tooltip: 'Clear',
child: Icon(Icons.clear),
),
);
}
// タッチした点をクリアする
void _clearPoints(){
setState((){
_points.clear();
});
}
// 点を追加
void _addPoint(TapDownDetails details) {
// setState()にリストを更新する関数を渡して状態を更新
setState(() {
_points.add(details.localPosition);
});
}
}
ややコードが長くなったが、主要なポイントを説明する。
- 4行目でタッチした点を記録しておくためOffsetのListを宣言している。これがこのクラスの状態となる。
- 12行目、
Scaffold
のbodyにはGestureDetector
を指定している。このウィジェットを使うことで、ユーザーのインタラクションを検出できる。ここでは、ユーザーのタッチに反応するため、onTapDownイベントを使った。 - 12行目、
GestureDetector
のchildに指定しているのはCustomPaint
である。このウィジェットはCustomPainter
を実装することで、背景に自在に描画することができる。実装の詳細は後述する。 - 19行目では
CustomPainter
のchildにCenter
ウィジェットを置いている。このようにしないと、GestureDetector
がタッチイベントに反応しないため、あえてこのようにしている。領域全体を占めるウィジェットであれば、Center
でなくても良いと思う。 - 39行目の_addPaint()はonTapDownイベント発生時に呼ばれるメソッドで、タッチされた座標をリストに追加している。Flutterでの決まりごととして、状態を更新するにはStateに用意されているsetState()メソッドに、更新のための関数を渡すことで行う必要がある。
次に、カスタム描画のコードを示す。
class MyPainter extends CustomPainter{
final List<Offset> _points;
final _rectPaint = Paint()..color = Colors.blue;
MyPainter(this._points);
@override
void paint(Canvas canvas, Size size) {
// 記憶している点を描画する
_points.forEach((offset) =>
canvas.drawRect(Rect.fromCenter(center: offset, width: 20.0, height: 20.0), _rectPaint));
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
カスタム描画を行うときはCustomPainter
を継承したクラスを作り、paint()とshouldRepaint()をオーバーライドする。paint()には実際の描画コードを書く。引数で渡ってくるCanvasには色々な描画メソッドが用意されている。ここでは、タッチした点を中心として大きさ20の四角形を描画している。この辺りの使い方はAndroidネイティブの描画方法とほぼ同じなため、すぐに馴染めると思う。shouldRepaint()はboolを返すメソッドで、再描画すべき時にはtrueを返す必要がある。このように簡単なサンプルでは常にtrueを返しても良いだろう。
これで、実装は完了した。最後に冒頭のMaterialAppのhomeを描画ウィジェットに置き換えるとアプリの完成である。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Pointer drawing lesson',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// MaterialAppのhomeに作成した描画ウィジェットを指定する
home: PointerDrawingWidget(title:'Pointer drawing lesson'),
);
}
}
このアプリの完全なコードはgithubにあるので参考にして欲しい。
ピンバック: 【Flutter】ProviderとConsumerを使った状態管理 – ザワプロ!