🐿️

【Flutter】Animationの基礎から応用まで ~②複数効果の適用とTweenSequence~

2022/11/12に公開

こちらの記事はアニメーションに関するシリーズ記事のvol2となります。

vol1ではアニメーションの基本に触れましたが、次はもう少し複雑なアニメーションを実現してみましょう

複数効果の適用とTweenSequence

複雑なアニメーションには以下の様なものが考えられます

  • 単体のWidgetに複数のアニメーション効果を同時に充てる
  • 単体のWidgetに同一のアニメーションを複数回充てる
  • 単体のWidgetに異なるアニメーションをそれぞれのタイミングで充てる
  • 複数のWidgetを連鎖的にアニメーションさせる
  • 単体 or 複数のWidgetを別々にアニメーションさせる

1つ1つ見ていきましょう

単体のWidgetに複数のアニメーション効果を同時に充てる

vol1.では単体のWidgetのAlignmentだけをアニメーションさせていましたが、例えば回転をさせながらAlignmentも変えたい場合はどうしたら良いでしょうか?

結論:1つのAnimationControllerから複数のアニメーションを生成し、単体のWidgetに充てる

いきなり結論から言ってしまいましたが、このユースケースはシンプルですね

加えたいアニメーション効果(位置、回転、色、サイズなど)の分、Tweenを用意し、そのTweenAnimationControllerを使ってAnimationを生成します

以下ではWidgetに位置(Alignment)と回転(rotation)の2つのアニメーション効果を掛けています

サンプルコード

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  late AnimationController controller;
  late Tween<Alignment> alignmentTween; // <<< 1つ目のアニメーションのTween
  late Tween<double> rotateTween; // <<< 2つ目のアニメーションのTween
  late Animation<Alignment> alignmentAnimation; // <<< 1つ目のアニメーション
  late Animation<double> rotateAnimation; // <<< 2つ目のアニメーション

  
  void initState() {
    controller = AnimationController(duration: Duration(seconds: 3),vsync: this);
    alignmentTween = Tween(begin: Alignment.topCenter,end: Alignment.bottomCenter); // <<< 位置のアニメーションの始点と終点を定義
    rotateTween = Tween(begin:0, end: 8 * pi); // <<< 回転のアニメーションの始点と終点を定義
    alignmentAnimation = controller.drive(alignmentTween); // <<< 位置のアニメーションを生成
    rotateAnimation = controller.drive(rotateTween); // <<< 回転のアニメーションを生成
    super.initState();
  }

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter app'),
      ),
      body: AnimatedBuilder(
        animation: controller,
        builder: (context, _){
          return Align(
            alignment: alignmentAnimation.value, // <<< 位置のアニメーション変化を適用
            child: Transform.rotate(
              angle: rotateAnimation.value, // <<< 回転のアニメーション変化を適用
              child:Text('Hello world!'),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          controller.forward();
        },
        backgroundColor: Colors.yellow[700],
        child: Icon(
          Icons.bolt,
          color: Colors.black,
        ),
      ),
    );
  }
}

単体のWidgetに同一のアニメーションを複数回充てる -TweenSequence-

次のユースケースでは同じアニメーション効果を複数回変化させながらWidgetに充てていきます

例えば以下の様にで四角の形にWidgetを動かしたい場合、位置を変化させるアニメーションを最初は右、次に下、その次に左、そして上と言うように変化させながら複数回充てる必要があります

このようなケースで使えるのがTweenSequenceです

TweenSequeceクラス

TweenSequenceはその名の通り、変化のSequence(順序)を定義したTweenです

TweenSequenceではTweenSequenceItemというどのくらいの間、どういった変化を加えるかを定義したクラスを配列で渡す事で複数回の変化を充てる事が出来ます

tweenSequnece = TweenSequence<T>([
    TweenSequenceItem(tween: Tween(),weight:1),
    TweenSequenceITem(tween: Tween(),weight:1),
    TweenSequenceITem(tween: Tween(),weight:1),
]);

TweenSequenceTweenと同じ抽象クラスであるAnimatableクラスを継承しており、要はTweenと同じように扱えます

なので定義したTweenSequenceAnimationControllerを使ってAnimationを生成する事が出来、このAnimationwをWidgetに適用する事で複数回変化するアニメーションをWidgetにさせる事が出来ます

animation = controller.drive(TweenSequence([]))

TweenSequenceItemクラス

TweenSequenceに複数渡すことが出来るTweenSequenceItemでは、どういった変化をtweenに、どのくらいの間をweightに定義します

TweenSequenceItem(
    tween: Tween(
    begin: const Alignment(-1, 3),
    end: Alignment.topLeft,
    ),
    weight: 2,
)

weightは時間の比率を表します。AnimationControllerに定義された時間(Duration)の間、どれくらいの間でtweenの変化を充てるかを決めます。

Flexibleクラスのflexと同じ様に、渡されているTweenSequenceItemweightの合計でDurationを割った分の時間がそのTweenSequenceItemの持ち時間になります

サンプルコード

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  late AnimationController controller;
  late TweenSequence<Alignment> tweenSequence; // <<< Tweenではなく、TweenSequenceとして定義
  late Animation<Alignment> animation;

  
  void initState() {
    controller = AnimationController(duration: Duration(seconds: 4),vsync: this);
    tweenSequence = TweenSequence<Alignment>([ // <<< 複数のTweenSequenceItemを受け取る
      TweenSequenceItem(
        tween: Tween(begin: Alignment.topLeft,end: Alignment.topRight),
        weight: 1 // <<< この例ではDurationが4秒なので、weight:1 = 1秒となる
      ),
      TweenSequenceItem(
        tween: Tween(begin: Alignment.topRight,end: Alignment.bottomRight),
        weight: 1
      ),
      TweenSequenceItem(
        tween: Tween(begin: Alignment.bottomRight,end: Alignment.bottomLeft),
        weight: 1
      ),
      TweenSequenceItem(
        tween: Tween(begin: Alignment.bottomLeft,end: Alignment.topLeft),
        weight: 1
      ),
    ]);
    animation = controller.drive(tweenSequence); // <<< TweenとAnimationControllerでAnimationを作るのと同じ様にしてAnimationを生成
    super.initState();
  }

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter app'),
      ),
      body: AnimatedBuilder(
        animation: controller,
        builder: (context, _){
          return Align(
            alignment: animation.value, // <<< animationを適用するだけ
            child: Text('Hello world!')
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          controller.forward();
        },
        backgroundColor: Colors.yellow[700],
        child: Icon(
          Icons.bolt,
          color: Colors.black,
        ),
      ),
    );
  }
}

サンプルコード

https://github.com/heyhey1028/flutter_samples/tree/main/samples/master_animation

続く

残りの3つのユースケースは次の記事に続きます

  • 単体のWidgetに異なるアニメーションをそれぞれのタイミングで充てる
  • 複数のWidgetを連鎖的にアニメーションさせる
  • 単体 or 複数のWidgetを別々にアニメーションさせる

https://zenn.dev/heyhey1028/articles/222e2851e9d97f

参考

Flutter大学

Discussion