Flutter

【Flutter】CustomPainterで星を描いて回転させる方法

Flutterでは自分で画像を用意して表示することができますが、自分で図形を描いて自由に動かすこともできます。

本記事では、CustomPainterを使用して星を描きつつ、描いた星を回転させる方法を備忘として残しておきます。

実際に回転させるとこんな感じになります。

また、記事後半にはCustomPainterを勉強するのにオススメのUdemy講座も載せています。是非、最後まで読んでいってください。

CustomPainterで図形を描くのは結構面白いのでオススメです。

Flutterで星(図形)を描く

Flutterで図形を描く際に以下のクラスを準備する必要があります。

  • CustomPaintクラス
  • CustomPainterを継承したクラス

以下で順番に見ていきます。

実際に作成した画面はこちらです。

CustomPaintクラス

CustomPaintクラスはSingleChildRenderObjectWidgetを継承したWidgetです。

図形を表示したい箇所で、このクラスを宣言していきます。

具体的には以下のようになります。

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Star'),
      ),
      backgroundColor: Colors.black,
      body: Center(
        child: CustomPaint(
          size: Size(double.infinity, double.infinity),
          painter: StarPainter(val: -animation.value),
        ),
      ),
    );

CustomPaintクラスではpainterという引数に、CustomPainterを継承したクラスを設定していきます。

CustomPainterを継承したクラス

CusomPainterクラスを継承したクラスを実装します。

その際に「paint」と「shouldRepaint」という2つのメソッドをOverrideして、実装する必要があります。

具体的なコードは以下のようになります。

class StarPainter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
    double centerWidth = size.width / 2;
    double centerHeight = size.height / 2;

    final _starOffsetList = <Offset>[
      Offset(centerWidth + 90, centerHeight + 120),
      Offset(centerWidth - 145, centerHeight - 45),
      Offset(centerWidth + 145, centerHeight - 45),
      Offset(centerWidth - 90, centerHeight + 120),
      Offset(centerWidth + 0, centerHeight - 145),
    ];

    final path = Path()..addPolygon(_starOffsetList, false);
    canvas.drawPath(
      path,
      Paint()
        ..color = Colors.yellow
        ..strokeWidth = 1.0,
    );
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

paintメソッドでは、図形を描くための処理を書いていきます。

引数のcanvasには円や四角や線を描く処理(drawXXX)が用意されているので、基本的な図形はそれを使えば簡単に書くことができます。

今回はdrawPathという処理を使用して星を描きました。

shouldRepaintメソッドでは、Widgetが再構築されたときに図形を再描画するかどうかを設定します。

trueにすると図形を再描画します。今回は星を回転させるため、trueにしておきます。

falseにすると再描画しません。再描画する必要がなければfalseにしておいたほうが良いでしょう。

描いた星を回転させる

次に描いた星を回転させます。

回転させる方法はFlutter標準搭載のAnimationを使用していきます。

コードは以下のようになります。

class StarScreen extends StatefulWidget {
  @override
  _StarScreenState createState() => _StarScreenState();
}

class _StarScreenState extends State<StarScreen> with SingleTickerProviderStateMixin {

  late Animation<double> animation;
  late AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(vsync: this, duration: Duration(seconds: 4));

    Tween<double> _rotationTween = Tween(begin: -pi, end: pi);

    animation = _rotationTween.animate(controller)
      ..addListener(() {
        setState(() {});
      })
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          controller.repeat();
        } else if (status == AnimationStatus.dismissed) {
          controller.forward();
        }
      });
    controller.forward();
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Star'),
      ),
      backgroundColor: Colors.black,
      body: AnimatedBuilder(
        animation: animation,
        builder: (BuildContext context, Widget? child) {
          return CustomPaint(
            size: Size(double.infinity, double.infinity),
            painter: StarPainter(val: -animation.value),
          );
        },
      ),
    );
  }
}

AnimatedBuilderでCustomPaintを返すようにします。その際に、Animationの値を渡していますが、この値はラジアンです。

AnimationControllerのDurationを4秒に設定しているので、4秒間で1週まわるようなラジアンをCustomPaintに渡すことができます。

このラジアンを使用して、星の位置を変えることで回転を実現することができます。

星の位置を変えるコードは以下のとおりです。

class StarPainter extends CustomPainter {
  final double val;

  StarPainter({required this.val});

  @override
  void paint(Canvas canvas, Size size) {
    double centerWidth = size.width / 2;
    double centerHeight = size.height / 2;

    final _starOffsetList = <Offset>[
      Offset(centerWidth + 90, centerHeight + 120),
      Offset(centerWidth - 145, centerHeight - 45),
      Offset(centerWidth + 145, centerHeight - 45),
      Offset(centerWidth - 90, centerHeight + 120),
      Offset(centerWidth + 0, centerHeight - 145),
    ];

    final _rotateOffsetList =
        _starOffsetList.map((o) => _rotate(o, val, centerWidth, centerHeight)).toList();

    final path = Path()..addPolygon(_rotateOffsetList, false);
    canvas.drawPath(
      path,
      Paint()
        ..color = Colors.yellow
        ..strokeWidth = 1.0,
    );
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }

  /// 回転の公式を利用し、指定したラジアンで回転させた場合のOffsetを返す
  /// Q((−)cos−(−)sin+, (−)sin+(−)cos+)
  Offset _rotate(Offset old, double radians, double centerWidth, double centerHeight) {
    final dx = (old.dx * cos(radians) - centerWidth * cos(radians)) -
        (old.dy * sin(radians) - centerHeight * sin(radians)) +
        centerWidth;
    final dy = (old.dx * sin(radians) - centerWidth * sin(radians)) +
        (old.dy * cos(radians) - centerHeight * cos(radians)) +
        centerHeight;
    return Offset(dx, dy);
  }
}

 

ポイントは_starOffsetListに含まれるすべてのOffsetに対して、渡されたラジアンを使用して回転の公式を用い、新しいOffsetを設定している点です。

まとめ

CustomPainterを使用して、自分で図形を描くことは難しそうな気がしていましたが、簡単な図形(円とか四角)であれば処理が用意されているので、数行で描くことができます。

Animationなどと組み合わせると、リッチなWidgetが作成できるため、うまく使っていくことで、イケてるアプリを作成できそうです。

CustomPainterについてしっかり学ぶには、動画学習サイトのUdemyで公開されている、CustomPainterでグラフを作成する講座がオススメです。

※割と初心者向けなのでFlutterに慣れている人にはオススメできません。

【Flutter】グラフ作成を通して図形描画に不可欠なCustomPainterを学習する

Flutter学習情報

Flutterを勉強するのに最適な参考書は、以下の「基礎から学ぶFlutter」です。

環境構築から、Dart/Flutterの基本/テストやパフォーマンスチューニングまで一通り学ぶことができます。

また、当ブログではFlutterを初心者が学ぶためにオススメな方法を「Flutter を初心者が学ぶおすすめの勉強法!【間違いない動画があります】」という記事で公開していますので、Flutterに興味がある方は是非読んでみてください。

Flutterに入門するためのオススメ勉強法【間違いない動画があります】Flutter 初心者が勉強する場合に、最高の動画をお伝えします。...

動画学習がしたい!という方にオススメの記事がこちらです。

【Udemy】スマホアプリ作成を学ぶときに受講しておきたい講座3選【Flutter】スマホアプリを初めて作成する場合、何らかの教材を見ながら進めると思います。 私は動画で学習するのが好きで、非常に効率よく学ぶことが...

Flutterの将来性はどうなのか?をまとめた記事はこちらです。

【2021/8 更新】Flutterの将来性をトレンドやGoogleの情勢から分析した結果クロスプラットフォーム開発のフレームワークの中で近年注目されているのが「Flutter」です。 私も2020年の4月頃から注目して...