Flutterで文字が消えたり出てきたりするLoading...画面を作る。

#この記事は
![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376889/a3e8c258-d754-59b1-3e34-b0962fae5858.gif)
こんな感じのロード画面を作る解説のような記事になります。

主にアニメーションの話になります。

####環境はFlutter 1.17.1になります。

#全体コード
```loading.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class Loader extends StatefulWidget {
  final Color color;
  final Duration duration;

  Loader({
    this.color = Colors.black,
    this.duration = const Duration(milliseconds: 1000),
  });

  @override
  _LoaderState createState() => _LoaderState();
}

class _LoaderState extends State<Loader> with SingleTickerProviderStateMixin {
  Animation<double> animation_1;
  Animation<double> animation_2;
  Animation<double> animation_3;
  Animation<double> animation_4;
  Animation<double> animation_5;
  Animation<double> animation_6;
  Animation<double> animation_7;
  Animation<double> animation_8;
  Animation<double> animation_9;
  Animation<double> animation_10;
  AnimationController controller;

  @override
  void initState() {
    super.initState();

    controller = AnimationController(
        duration: widget.duration, vsync: this);

    animation_1 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.0, 0.70, curve: Curves.linear),
      ),
    );

    animation_2 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.1, 0.80, curve: Curves.linear),
      ),
    );

    animation_3 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.2, 0.90, curve: Curves.linear),
      ),
    );

    animation_4 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.3, 1.00, curve: Curves.linear),
      ),
    );

    animation_5 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.4, 1.10, curve: Curves.linear),
      ),
    );

    animation_6 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.5, 1.20, curve: Curves.linear),
      ),
    );

    animation_7 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.6, 1.30, curve: Curves.linear),
      ),
    );

    animation_8 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.7, 1.40, curve: Curves.linear),
      ),
    );

    animation_9 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.8, 1.50, curve: Curves.linear),
      ),
    );

    animation_10 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.9, 1.60, curve: Curves.linear),
      ),
    );

    controller.addListener(() {
      setState(() {
     
      });
    });

    controller.repeat();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Opacity(
            opacity: (animation_1.value <= 0.4
                ? 2.5 * animation_1.value
                : (animation_1.value > 0.40 && animation_1.value <= 0.60)
                ? 1.0
                : 2.5 - (2.5 * animation_1.value)),
            child: Padding(
              padding: const EdgeInsets.only(right: 5.0),
              child: _Item(
                text: 'L',
                color: widget.color,
              ),
            ),
          ),
          Opacity(
            opacity: (animation_2.value <= 0.4
                ? 2.5 * animation_2.value
                : (animation_2.value > 0.40 && animation_2.value <= 0.60)
                ? 1.0
                : 2.5 - (2.5 * animation_2.value)),
            child: Padding(
              padding: const EdgeInsets.only(right: 5.0),
              child: _Item(
                text: 'o',
                color: widget.color,
              ),
            ),
          ),
          Opacity(
            opacity: (animation_3.value <= 0.4
                ? 2.5 * animation_3.value
                : (animation_3.value > 0.40 && animation_3.value <= 0.60)
                ? 1.0
                : 2.5 - (2.5 * animation_3.value)),
            child: Padding(
              padding: const EdgeInsets.only(right: 5.0),
              child: _Item(
                text: 'a',
                color: widget.color,
              ),
            ),
          ),
          Opacity(
            opacity: (animation_1.value <= 0.4
                ? 2.5 * animation_1.value
                : (animation_1.value > 0.40 && animation_1.value <= 0.60)
                ? 1.0
                : 2.5 - (2.5 * animation_1.value)),
            child: Padding(
                padding: const EdgeInsets.only(right: 5.0),
                child: _Item(
                  text: 'd',
                  color: widget.color,
                ),
            ),
          ),
          Opacity(
            opacity: (animation_2.value <= 0.4
                ? 2.5 * animation_2.value
                : (animation_2.value > 0.40 && animation_2.value <= 0.60)
                ? 1.0
                : 2.5 - (2.5 * animation_2.value)),
            child: Padding(
                padding: const EdgeInsets.only(right: 5.0),
                child: _Item(
                  text: 'i',
                  color: widget.color,
                ),
            ),
          ),
          Opacity(
            opacity: (animation_3.value <= 0.4
                ? 2.5 * animation_3.value
                : (animation_3.value > 0.40 && animation_3.value <= 0.60)
                ? 1.0
                : 2.5 - (2.5 * animation_3.value)),
            child: Padding(
                padding: const EdgeInsets.only(right: 5.0),
                child: _Item(
                  text: 'n',
                  color: widget.color,
                ),
            ),
          ),
          Opacity(
            opacity: (animation_1.value <= 0.4
                ? 2.5 * animation_1.value
                : (animation_1.value > 0.40 && animation_1.value <= 0.60)
                ? 1.0
                : 2.5 - (2.5 * animation_1.value)),
            child: Padding(
                padding: const EdgeInsets.only(right: 8.0),
                child: _Item(
                  text: 'g',
                  color: widget.color,
                ),
            ),
          ),
          Opacity(
            opacity: (animation_2.value <= 0.4
                ? 2.5 * animation_2.value
                : (animation_2.value > 0.40 && animation_2.value <= 0.60)
                ? 1.0
                : 2.5 - (2.5 * animation_2.value)),
            child: Padding(
                padding: const EdgeInsets.only(right: 8.0),
                child: _Dot(
                  radius: 5.0,
                  color: widget.color,
                ),
            ),
          ),
          Opacity(
            opacity: (animation_3.value <= 0.4
                ? 2.5 * animation_3.value
                : (animation_3.value > 0.40 && animation_3.value <= 0.60)
                ? 1.0
                : 2.5 - (2.5 * animation_3.value)),
            child: Padding(
                padding: const EdgeInsets.only(right: 8.0),
                child: _Dot(
                  radius: 5.0,
                  color: widget.color,
                ),
            ),
          ),
          Opacity(
            opacity: (animation_1.value <= 0.4
                ? 2.5 * animation_1.value
                : (animation_1.value > 0.40 && animation_1.value <= 0.60)
                ? 1.0
                : 2.5 - (2.5 * animation_1.value)),
            child: Padding(
                padding: const EdgeInsets.only(right: 8.0),
                child: _Dot(
                  radius: 5.0,
                  color: widget.color,
                ),
            ),
          ),
        ],
      ),
    );
  }

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

class _Item extends StatelessWidget {
  final String text;
  final Color color;

  _Item({
    this.text,
    this.color
  });

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Transform.rotate(
        angle: 0.0,
        child: Container(
          width: 20,
          height: 20,
          child: Text(
            text
          ),
        ),
      ),
    );
  }
}

class _Dot extends StatelessWidget {
  final double radius;
  final Color color;

  _Dot({this.radius, this.color});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Transform.rotate(
        angle: 0.0,
        child: Container(
          width: radius,
          height: radius,
          decoration: BoxDecoration(color: color, shape: BoxShape.circle),
        ),
      ),
    );
  }
}
```

#適所解説
Flutterでアニメーションを実装するにあたって押さえておかないといけない項目が4つあります。

####Animation
Animationクラスはアニメーションの状態を保持します。ここの変更に応じて画面を書き換えるようにすることでアニメーションを実装していきます。

今回のコードで言うと

```loading.dart
Animation<double> animation_1;
Animation<double> animation_2;
Animation<double> animation_3;
Animation<double> animation_4;
Animation<double> animation_5;
Animation<double> animation_6;
Animation<double> animation_7;
Animation<double> animation_8;
Animation<double> animation_9;
Animation<double> animation_10;
```
ここが該当します。
Animationクラスにanimation_xを保持させ、次に解説するAnimationControllerで扱います。

####AnimationController
AnimationControllerでは実際にアニメーションを制御します。また、与えられた条件に従いAnimationクラスの状態を変更する部分です。

今回のコードでは

```loading.dart
controller = AnimationController(
        duration: widget.duration, vsync: this);

    animation_1 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.0, 0.70, curve: Curves.linear),
      ),
    );

    animation_2 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.1, 0.80, curve: Curves.linear),
      ),
    );

    animation_3 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.2, 0.90, curve: Curves.linear),
      ),
    );

    animation_4 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.3, 1.00, curve: Curves.linear),
      ),
    );

    animation_5 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.4, 1.10, curve: Curves.linear),
      ),
    );

    animation_6 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.5, 1.20, curve: Curves.linear),
      ),
    );

    animation_7 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.6, 1.30, curve: Curves.linear),
      ),
    );

    animation_8 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.7, 1.40, curve: Curves.linear),
      ),
    );

    animation_9 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.8, 1.50, curve: Curves.linear),
      ),
    );

    animation_10 = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(0.9, 1.60, curve: Curves.linear),
      ),
    );

    controller.repeat();
  }
```

ここの部分が該当します。

controllerに次に解説するTweenで条件を定義していきます。

####Tween
Tweenでは数値の間隔を定義します。ここの数値に合わせてアニメーションの動きを表します。

今回のロード画面で言うと、各文字がアニメーションする範囲の開始と終了をここで決めています。

####addListener
Animationは全てのリスナーに変更を通知します。controllerへTwennで状態を定義した後、setStateを呼び出すことによって実際に状態を描画します。

```loading.dart
controller.addListener(() {
      setState(() {
     
      });
    });
```

基本的にこの4点を押さえていればアニメーションを組むことは可能だと思います。

#アニメーションを組んでみた個人的所感
基本の4点にプラスアルファを付け足せばアニメーションはチョチョイのちょいです。

すごく簡単にアニメーションが組めるので皆さんもいろいろ調べてみてください。

#参考文献

[mono氏のすごくわかりやすいアニメーションWidget全紹介の記事](https://medium.com/flutter-jp/transition-9c57528c84b8)

[Flutter公式youtubeチャンネル](https://www.youtube.com/results?search_query=flutter+widget+of+the+week)