はじめに
3年ほどFlutterを触らせてもらっているのにも関わらずモバイルの手書き機能を実装したことがなかったため今回の記事を作成しました。
今回はそこで使用したCustomPainterを紹介します。
CustomPainterとは
CustomPainterは、Flutterで独自の描画(カスタムペイント)を行うためのクラスです。
Widgetツリーで通常のWidgetではできない自由なグラフィックス(図形・線・テキスト・画像など)をCanvas上に描画できます。
主に以下の用途で使われます:
- お絵描き機能
- グラフやチャートの描画
- アニメーションのカスタム描画
- ゲームや特殊UI
今回はこの中のお絵描き機能の方を実装してみました。
実装
実装方法基本構造としては以下になると思います。
- CustomPainterクラスを継承したクラスを作る
- paint(Canvas canvas, Size size)メソッド内で描画処理を書く
- shouldRepaintで再描画の必要性を返す
- CustomPaintウィジェットにPainterを渡して利用
上記を加味して手書きだけするコードを作成しました。
import 'package:flutter/material.dart';
void main() => runApp(const DrawApp());
class DrawApp extends StatelessWidget {
const DrawApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(body: DrawingBoard()),
);
}
}
class DrawingBoard extends StatefulWidget {
const DrawingBoard({super.key});
@override
_DrawingBoardState createState() => _DrawingBoardState();
}
class _DrawingBoardState extends State<DrawingBoard> {
List<Offset?> points = [];
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (details) {
setState(() {
points.add(details.localPosition);
});
},
onPanEnd: (details) {
setState(() {
points.add(null); // 線の区切り
});
},
child: CustomPaint(
painter: MyPainter(points),
size: Size.infinite,
),
);
}
}
class MyPainter extends CustomPainter {
final List<Offset?> points;
MyPainter(this.points);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black
..strokeWidth = 4.0
..strokeCap = StrokeCap.round;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i]!, points[i + 1]!, paint);
}
}
}
@override
bool shouldRepaint(MyPainter oldDelegate) => true;
}
解説
DrawingBoard(手書きボード)
class DrawingBoard extends StatefulWidget {
const DrawingBoard({super.key});
@override
_DrawingBoardState createState() => _DrawingBoardState();
}
手書きの状態(座標や線)を持つのでStatefulWidgetを使用しています。
class _DrawingBoardState extends State<DrawingBoard> {
List<Offset?> points = [];
pointsは指やペンが動いた位置(座標)を記録するリストです。
Offsetは2D座標(x, y)を表します。
nullは線の区切り(指を離したタイミング)を示します。
GestureDetector(ジェスチャー検知)
return GestureDetector(
onPanUpdate: (details) {
setState(() {
points.add(details.localPosition);
});
},
onPanEnd: (details) {
setState(() {
points.add(null); // 線の区切り
});
},
child: CustomPaint(
painter: MyPainter(points),
size: Size.infinite,
),
);
- GestureDetectorで指の動きを検知
- onPanUpdate: 画面上を指やペンが動くたび、その座標をpointsに追加
- onPanEnd: 指やペンが離れたとき、nullを追加して線の区切りを明示
- CustomPaintにMyPainterを渡して、pointsに従って描画します
MyPainter(カスタムペインター)
class MyPainter extends CustomPainter {
final List<Offset?> points;
MyPainter(this.points);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black
..strokeWidth = 4.0
..strokeCap = StrokeCap.round;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i]!, points[i + 1]!, paint);
}
}
}
@override
bool shouldRepaint(MyPainter oldDelegate) => true;
}
- paintメソッドで、pointsの座標同士を線で結びながら描画
- paintの設定で線の色や太さを指定
- shouldRepaintは毎回再描画するためtrueを返す
以上になります。
最後に
これで手書きができるようになりました!
ただこのコードだと手書きができるだけなので、消しゴム、色変更、保存などはできないです。別途追加コードが必要になります。
私のように手書き機能やったことないなって人の参考程度になればなと思います。
