【CustomPainter】Flutter2.2でグリッド表示機能を作る

MVVMで作っててちょっと疲れたので今回は機能だけを作ろうと思います。

ウィンドウを自由に配置できると言っても小数点単位でズレてしまうので、 グリッドを表示しているときは吸着してくれるような仕組みを作りたいです。

機能追加はあとからやるとして、ともかくグリッド線を描写するところから始めましょう。

事前に言っておくと、グリッド線は基本的に図形を描写していく形になります。

drawRectとdrawLineのどっちかを書くかは自由です。

私は今回drawLineを使ってみようと思います。

Z軸的には一番下に描写すべきですね。

今のところはウィンドウを自由に配置するところでしか使わないので、 layoutedit_page.dartに組み込んでいこうと思います。

もしどこかで使い回すようなことがあったらウィジェットを独立させて 呼び出せるようにしたほうがいいかもしれませんね。

それではまずなんでもいいのでdrawLineで線を引いてみましょう。

CustomPainterウィジェットの使い方を覚える

図形等を描写するために用いるのがCustomPainterです。

図形をなにか作る際は基本的にはCustomPainterを継承する必要があります。

最低限オーバーライドしなきゃいけないのが「paint」「shouldRepaint」メソッドです。

paintはその名の通り描写用メソッドで、shouldRepaintは再描写をするかどうかですね。

今回はグリッド線を描写したり消したりしたいのでshouldRepaintはtrueを渡すことになります。

まずはCustomPainterを継承した最低限のクラスを作ってみましょう。

class _LinePainter extends CustomPainter {
  // 再描写をするかどうか
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }

  // 実際の描写関数
  @override
  void paint(Canvas canvas, Size size) {}
}

次にpaint関数の中身を入れていきます。

class _LinePainter extends CustomPainter {
  // 再描写をするかどうか
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }

  // 実際の描写関数
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint();
    paint.color = Colors.red;
    paint.strokeWidth = 5;
    canvas.drawLine(Offset(200, 0), Offset(200, 500), paint);
  }
}

Paintクラスのインスタンスを作りそれに対してプロパティを設定していき canvas.drawLineの引数に視点と終点の情報を記載して先程作ったペイントのインスタンスを渡します。

X軸200px、Y軸0pxから真下に500px線を引くという感じになります。

次定義したクラスを呼び出すステートレスウィジェットを作成しましょう。

class _GridLineWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: LayoutBuilder(
        builder: (BuildContext context, BoxConstraints constraints) {
          return Container(
            child: CustomPaint(
              painter: _LinePainter(),
            ),
          );
        },
      ),
    );
  }
}

一応SafeAreaにしてますがContainerでも大丈夫です。

そしてこのウィジェットを_LayouteditPageBodyStateクラスで呼び出します。

class _LayouteditPageBodyState extends State<LayouteditPageBody> {
  @override
  void initState() {
    super.initState();

    var viewModel = Provider.of<LayouteditViewModel>(context, listen: false);
  }

  @override
  Widget build(BuildContext context) {
    return Stack(children: [
      _absoluteWindow(),
      _GridLineWidget(),
    ]);
  }
}

Stackなので最初にあるウィジェットほど下に位置します。

今回はわかりやすいように絶対配置ウィンドウの上に線がくるようにしました。

これで実行すると青の矩形の隣に赤い縦線が入るはずです。

格子状に線を引いてグリッドにする

線を引けることがわかったので、最初に作った_LinePainterを改良しましょう。

グリッド状に線を引くということは一定の間隔で縦と横に線を引けばよさそうです。

繰り返し処理を使えば簡単そうです。

問題はどこまでどの高さ・横幅までやるかになりますが 理想的にはウィンドウの縦横幅を取得してそこが最終ラインにしたいところですが まだそれは覚えていないので一旦リテラルでグリッドを引いてみましょう。

  void paint(Canvas canvas, Size size) {
    final paint = Paint();
    paint.color = Colors.grey;
    paint.strokeWidth = 1;
    for (var i = 0; i < 50; i++) {
      canvas.drawLine(Offset(0, i * 32), Offset(1000, i * 32), paint);
      canvas.drawLine(Offset(i * 32, 0), Offset(i * 32, 1000), paint);
    }
  }

とりあえずリテラルですがいい感じに格子状に引けるようになったと思います。

後は引数としてグリッドのサイズや線同士の間隔にしたり 非表示機能を設けたりするとよさげです。

非表示は色を透明にして再描写してもいいですが、描写をクリアしたほうがちょっとは軽いかも知れません。

これでグリッド表示機能の基礎は出来ました。

次回はウィンドウの横幅と縦幅を取得したりしてグリッド機能を完成させましょう。