【Flutter】IntervalでシーケンシャルなAnimationを実現する


# はじめに

この様に拡大とフェードアウトをシーケンシャルに設定したい場合にIntervalを使うと良い感じに書けたので紹介します。

![Nov-20-2021 21-54-26.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200963/3015e716-6164-b2dd-3a01-f03615423251.gif "Nov-20-2021 21-54-26.gif")

# AnimationControllerのwhenCompleteを使う方法

Flutter初心者の私がシーケンシャルなAnimationを設定する方法としてまず辿り着いたのがこの方法です。

まず、拡大用のAnimationControllerとフェードアウト用のAnimationControllerをそれぞれ用意します。
そして、次の様なロジックで動かします。

1. 拡大用のAnimationを再生
2. 拡大用のAnimationが完了したら、とフェードアウト用のAnimationを再生
3. フェードアウト用のAnimationが完了したら、拡大用のAnimationを再生

コード

``` Dart
class _HomeWidgetState extends State<HomeWidget> with TickerProviderStateMixin {
  late AnimationController _fadeAnimCtlr;
  late Animation<double> _fadeAnimation;
  late AnimationController _scaleAnimCtlr;
  late Animation<double> _scaleAnimation;

  @override
  void initState() {
    _fadeAnimCtlr = AnimationController(vsync: this, duration: Duration(milliseconds: 1000));
    _fadeAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(_fadeAnimCtlr)
      ..addListener(() {
        setState(() {});
      });
    _scaleAnimCtlr = AnimationController(vsync: this, duration: Duration(milliseconds: 1000));
    _scaleAnimation = Tween<double>(begin: 1.0, end: 3.0).animate(_scaleAnimCtlr)
      ..addListener(() {
        setState(() {});
      });
    repeatAnimation();
  }

  void repeatAnimation() {
    _scaleAnimCtlr.forward().whenComplete(() {
      _fadeAnimCtlr.forward().whenComplete(() {
        _scaleAnimCtlr.reset();
        _fadeAnimCtlr.reset();
        repeatAnimation();
      });
    });
  }

  @override
  void dispose() {
    _fadeAnimCtlr.dispose();
    _scaleAnimCtlr.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    var screenSize = MediaQuery.of(context).size;
    return Stack(
      children: <Widget>[
        Positioned(
          top: screenSize.height * 0.2,
          left: screenSize.width * 0.5,
          child: ScaleTransition(
            scale: _scaleAnimation,
            child: FadeTransition(
                opacity: _fadeAnimation,
                child: Text("Hello\nWorld",
                    style: TextStyle(
                      fontSize: 20,
                      color: Colors.white,
                    ))),
          ),
        ),
      ],
    );
  }
}
```

# Intervalを使う方法

次に[ここの情報](https://flutterbyexample.com/lesson/using-tweens-and-intervals)から学んだIntervalを使った方法です。

拡大フェードアウト用のひとつのAnimationControllerを用意し、Intervalを使ってそれぞれのAnimationが動くタイミングを指定します。
全体として2秒のAnimationの前半を拡大、後半をフェードアウトを動作させるというイメージです。

コード

``` Dart
class _HomeWidgetState extends State<HomeWidget> with SingleTickerProviderStateMixin {
  late AnimationController _fadeScaleAnimCtlr;
  late Animation<double> _fadeAnimation;
  late Animation<double> _scaleAnimation;

  @override
  void initState() {
    _fadeScaleAnimCtlr = AnimationController(vsync: this, duration: Duration(milliseconds: 2000));
    _fadeAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
      CurvedAnimation(parent: _fadeScaleAnimCtlr, curve: Interval(0.5, 1.0)))
      ..addListener(() {
        setState(() {});
      });
    _scaleAnimation = Tween<double>(begin: 1.0, end: 3.0).animate(
      CurvedAnimation(parent: _fadeScaleAnimCtlr, curve: Interval(0.0, 0.5)))
      ..addListener(() {
        setState(() {});
      });
    _fadeScaleAnimCtlr.repeat();
  }

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

  @override
  Widget build(BuildContext context) {
    var screenSize = MediaQuery.of(context).size;
    return Stack(
      children: <Widget>[
        Positioned(
          top: screenSize.height * 0.2,
          left: screenSize.width * 0.5,
          child: ScaleTransition(
            scale: _scaleAnimation,
            child: FadeTransition(
                opacity: _fadeAnimation,
                child: Text("Hello\nWorld",
                    style: TextStyle(
                      fontSize: 20,
                      color: Colors.white,
                    ))),
          ),
        ),
      ],
    );
  }
}
```

# まとめ

前者の方法も後者の方法もメリット、デメリットがあると思います。

||メリット|デメリット|
|:---:|:----|:-------|
| whenComplete |直感的に分かりやすい|Animation毎にContorllerが必要 <br> Tickerが複数必要なのでアプリの動作コストが高い|
| Interval |AnmationContorllerがひとつで済む <br> Tickerがひとつで済むのでアプリの動作コストが低い|直感的に分かりづらい|

恐らく、Animationを設定する対象(Widget)が同じであればIntervalを使った方がすっきりして良いと思いますが、別であればAnimationControllerを分けた方が後々良かったりするのではないかと思っています。