torikatsu.dev

Flutterとかプログラミングとかガジェットとか書きます

Flutterで菱形を描画する

こんばんは。最近はFlutterを使ってアプリ開発をしているのですが、Flutterで図形を描画する方法に少しハマりました。

そこで今回の記事では個人的に得られた知見を共有したいと思います。

まだFlutterの経験が浅いため、誤った解釈をしている部分があればご指摘いただければ幸いです。

手順

任意の図形を描画するときは以下の手順に従います。

Flutterで図形を描画する時のイメージは、図形を描くペンと、図形の頂点の座標一覧の二つを予め用意しておき、その二つを元にFlutterが図形を描画してくれる感じでいいと思います。

Flutterが図形を描画するイメージ
描画のイメージ

今回は例として菱形を描画してみたいと思います。

1.CustomPaintを継承したクラスを作成する

Flutterで図形の描画を担うのはCustomPainterクラスです。
CustomPainterクラスはFlutterの標準ライブラリに含まれているのでパッケージを追加する必要はありません。

図形を描画する際はCustomPainterクラスを継承したクラスを自作して、paint()shouldRepaint()をオーバーライドする必要があります。

それぞれの役割は次の通りです。

  • paint(Canvas canvas, Size size)

描画する図形を指定する

canvasは言葉の通り図形を描く座標平面みたいなもので、sizeはWidgetツリーの親要素のサイズと考えて良さそうです。

  • shouldRepaint(CustomPainter oldDelegate)

再描画に関する設定をする

以下のスニペットはCustomPainterクラスを継承したクラスです。 この状態ではまだなんの図形も描画しません。

class MyPainter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
  }
}

2.Paintインスタンスを生成する

図形を描画するペンの役割を担うPaintインスタンスを生成します。

Paintインスタンスは様々な値を設定することができます。 詳細は以下のリファレンスが参考になります。

api.flutter.dev

ここでは黒色で太さが1のペンを指定します。

class MyPainter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
    const paint = Paint();
    paint.color = Colors.black;
    paint.strokeWidth = 1.0;
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
  }
}

3.Pathインスタンスを生成し、図形の頂点の座標をせっていする

つぎにPathインスタンスを生成し、図形の頂点を与えます。 ここでは菱形を描くので(1, 0), (1, 2), (2, 1), (0, 1)の4つの点を与えます。

ここで気をつけなければならないのは図形を座標を負の数にしないことです。 もちろん負の数でも描画はできるのですが、負の数で座標を指定するとCenterなどでWrapした時に図形の中心がずれてしまいます。

CustomPainterは図形の描画のみに集中し、センタリングや大きさなどはContainer()Center()などでラップしてあげる方が図形に関する関心ごとを分離できて再利用性が高まるので、個人的には全て正の数で描画するのがおすすめです。

またpaint()の引数で渡ってくるSizeを利用して座標を指定するとより柔軟な図形を描くことができます。

Path().moveTo(x, y)でペンの位置を任意の場所に移動することができ、Path().lineTo(x, y)で現在のペンの位置から引数で指定した座標まで線を引くことができます。 線を引き終わったらPath().close()で描画を終了します。

ここではdartのカスケードが使えるので是非使いましょう。

class MyPainter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
    const paint = Paint();
    paint.color = Colors.black;
    paint.strokeWidth = 1.0;
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    /// ペンの設定
    const paint = Paint();
    paint.color = Colors.black;
    paint.strokeWidth = 1.0;

    /// 座標の設定
    ..moveTo(size.width / 2, 0)
        ..lineTo(size.width, size.height / 2)
        ..lineTo(size.width / 2, size.height)
        ..lineTo(0, size.height / 2)
        ..lineTo(size.width / 2, 0)
        ..close();
  }
}

これでペンと座標の設定が終わりました。あと一息です。

4.canvasにPathインスタンスを渡す

さいごに今まで準備したPath()Paint()paintの第一引数に渡ってくるcanvasに渡して完成です。

class MyPainter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
    const paint = Paint();
    paint.color = Colors.black;
    paint.strokeWidth = 1.0;
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    /// ペンの設定
    const paint = Paint();
    paint.color = Colors.black;
    paint.strokeWidth = 1.0;

    /// 座標の設定
    ..moveTo(size.width / 2, 0)
        ..lineTo(size.width, size.height / 2)
        ..lineTo(size.width / 2, size.height)
        ..lineTo(0, size.height / 2)
        ..lineTo(size.width / 2, 0)
        ..close();

    /// キャンバスにペンと座標を渡す
    canvas.drawPath(path, paint);
  }
}

以上で菱形を描画することができました!お疲れ様です!

おわりに

Flutterは公式パッケージに用意されているWidgetがとても充実しているのでいざオリジナルの図形を描画するとなると面食らってしまいますが、実際にコードを書いてみると意外と単純で、とっつきやすいと思います。

それでは、よいflutterライフを!