【Flutter】Animated?Transition?もう迷わない!アニメーションに最適なWidgetの選び方を丁寧に解説してみた

Dart

概要

どうも、@daiki1003です!

Flutterでアニメーションを実装したいけど、アニメーション実装するための方法がありすぎて良く分からん…。

そんなあなたに向けた記事を書きましたので是非読んでみてください。

参考とした動画

先に言ってしまうと本記事はFlutterチームが出している下記の動画を
和訳してまとめた感じの記事です。
本当にわかりやすい動画だったので記事化したくて書きました。

How to choose which Flutter Animation Widget is right for you? – Flutter in Focus

まとめ画像

この画像だけでも覚えて帰ってね。

画像を見てわからないところは下記で説明しているので
読んでみてください。
それでも分からなければ @daiki1003に質問して下さい!

では、詳細にまいりましょう。

1. お絵かきの様なアニメーションかどうか?

まず、最初はこの質問からです。

お絵かきの様な…と言うのは例えば、鳥が羽ばたくとかそういった類のものです。
それともプリミティブなウィジェット(Row, Column…)が対象なのか。

yes→2へ。
no→3へ。

2. 画像で表現出来る?

大変そうですね、心中お察しします笑

そのアニメーションはベクタ画像やラスタ画像などを使って、アニメーションは実現出来ますか?
出来るのであれば、rivelottieなどを使って表現してみましょう!

画像で表現出来ない場合は?

その場合は、CustomPainter
を継承したクラスを作成し、描画を行いましょう。
実装例は以下です。

class Sky extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    var rect = Offset.zero & size;
    var gradient = RadialGradient(
      center: const Alignment(0.7, -0.6),
      radius: 0.2,
      colors: [const Color(0xFFFFFF00), const Color(0xFF0099FF)],
      stops: [0.4, 1.0],
    );
    canvas.drawRect(
      rect,
      Paint()..shader = gradient.createShader(rect),
    );
  }

  @override
  SemanticsBuilderCallback get semanticsBuilder {
    return (Size size) {
      // Annotate a rectangle containing the picture of the sun
      // with the label "Sun". When text to speech feature is enabled on the
      // device, a user will be able to locate the sun on this picture by
      // touch.
      var rect = Offset.zero & size;
      var width = size.shortestSide * 0.4;
      rect = const Alignment(0.8, -0.9).inscribe(Size(width, width), rect);
      return [
        CustomPainterSemantics(
          rect: rect,
          properties: SemanticsProperties(
            label: 'Sun',
            textDirection: TextDirection.ltr,
          ),
        ),
      ];
    };
  }

  // Since this Sky painter has no fields, it always paints
  // the same thing and semantics information is the same.
  // Therefore we return false here. If we had fields (set
  // from the constructor) then we would return true if any
  // of them differed from the same fields on the oldDelegate.
  @override
  bool shouldRepaint(Sky oldDelegate) => false;
  @override
  bool shouldRebuildSemantics(Sky oldDelegate) => false;
}

CustomPainterは上手く使えば、効率的にアニメーションを描画できますが
下手すると…です。
パフォーマンス監視ツールなどを駆使して上手に実装しましょう。

こちらはかなり上級者向け、修羅の道です笑

3. 永遠?不連続的?協調?

次の3つの質問、

どれか一つでもyes→4へ。
全てno→6へ。

そのアニメーションは永遠に続く?

・1秒毎にランダムなカラーに変更する
・ずっと回転してる

みたいなアニメーションかどうかです。

不連続的なアニメーションか?

不連続、と言うのはアニメーションが0から1、0から1、0から1…と言う様に繰り返すか?
と言うことです。
動画では円が小さい→大きい、小さい→大きい
と言うアニメーションのが例となっています。

複数のウィジェットを協調して動かしたいか?

一つだけではなく、複数のウィジェットをタイミングを見計って動かしたいかどうかです。

動画では四角がウェーブする様なアニメーションが例となっています。

4. FooTransitionに欲しいものはあるか?

このセクションに来た場合、あなたが求めているアニメーションは

Explicit Animation(明示的なアニメーション)

と言うものになります。

AnimationControllerを用意し、必要なタイミング(ボタンを押されたなど)でcontrollerに対して
forward/reverse/stopなどを明示的に呼ぶからです。

Flutter側でContainerSizeTransition、ScaleTransitionなどを用意してくれています。
望むプロパティを変更出来るクラスはありますか?

ある→それを使って実装。
ない→5へ。

5. そのウィジェットは独立しているか?

クラスとしてアニメーションを作成したいかどうかです。

yes→AnimatedWidget
をサブクラス化してアニメーションを実装しましょう!
実装例は以下です。

class SpinningContainer extends AnimatedWidget {
  const SpinningContainer({Key key, AnimationController controller})
      : super(key: key, listenable: controller);

  Animation get _progress => listenable;

  @override
  Widget build(BuildContext context) {
    return Transform.rotate(
      angle: _progress.value * 2.0 * math.pi,
      child: Container(width: 200.0, height: 200.0, color: Colors.green),
    );
  }
}

no→AnimatedBuilder
使ってアニメーションを実装します。
実装例は以下です。

final animation = Tween(begin: 0, end: 2 * pi).animate(controller);
AnimatedBuilder(
  animation: animation,
);

※パフォーマンスが心配…?

さて、実際に望む通りのアニメーションをFooTransitionなりAnimatedWidgetなりで
実装してみた。
けど、パフォーマンスが悪い…。

そんな時は、上で紹介したCustomPainterを使ってみましょう。

6. AnimatedFooに欲しいものはあるか?

このセクションに来た場合、あなたが求めているアニメーションは

Implicit Animation(暗黙的なアニメーション)

と言うものになります。

AnimationControllerは自前で用意せず、Flutter側で用意してくれた
クラスが内包してくれています。
プロパティを変更するだけで、アニメーションをしてくれるのでとてもお手軽に実装出来ます。

例えば、AnimatedContainer, AnimatedSizeなどを用意してくれています。
自分が変えたいプロパティがこれらの中に含まれているとしたらこれらを使いましょう!

どんなものがあるかやそれぞれの使い方はmonoさんが詳しく説明してくれているこちらのリンクがおすすめです。

Flutterのお手軽にアニメーションを扱えるAnimated系Widgetをすべて紹介
シンプルなアニメーションはImplicitlyAnimatedWidgetで扱うと楽に組めます

おおよそ、これだけで実装出来ます。

AnimatedPositioned(
  top: _top,
  duration: const Duration(milliseconds: 300),
  child: Container(
    width: 200,
    height: 200,
    color: Colors.blue,
  ), 
),

この状態で

setState(() {
  _top = 300;
});

で終わり。簡単でしょ?

TweenAnimationBuilderを使う

AnimatedFooがない場合、TweenAnimationBuilder
使って実装してみましょう。
実装例は以下です。

TweenAnimationBuilder(
  tween: Tween(begin: 0, end: targetValue),
  duration: Duration(seconds: 1),
  builder: (BuildContext context, double size, Widget child) {
    return IconButton(
      iconSize: size,
      color: Colors.blue,
      icon: child,
      onPressed: () {
        setState(() {
          targetValue = targetValue == 24.0 ? 48.0 : 24.0;
        });
      },
    );
  },
  child: Icon(Icons.aspect_ratio),
);

これでボタンを押すたびに、アイコンが24になったり48になったりします。

最後に

「次回以降も記事を読んでみたい!」
「この辺分からなかったから質問したい!」

そんな時は、是非@daiki1003のフォローお願いします♪

コメント

タイトルとURLをコピーしました