Flutterのアニメーションを理解する(後編)

この記事はFlutter 全部俺 Advent Calendar 16日目の記事です。

このアドベントカレンダーについて

このアドベントカレンダーは @itome が全て書いています。

基本的にFlutterの公式ドキュメントとソースコードを参照しながら書いていきます。誤植や編集依頼はTwitterにお願いします。

応用的なアニメーションを作る手順

Flutterのカスタムアニメーションを作るときは、

の順番で考えていきます。

AnimationControllerの用意

アニメーションを扱うWidgetはTickerProviderStateMixinなどを使いやすいように、 StatefulWidgetにしておくのがいいです。

まずは、initStateActionControllerを用意しておきましょう。

AnimationController _controller;

@override
void initState() {
  super.initState();
  _controller = AnimationController(
    duration: const Duration(milliseconds: 300),
    vsync: this,
  );
}

各種Tweenの割り当て

作ったActionCreatorTweenを割り当てて0.0~1.0double値を 必要な値にマッピングしたAnimationを作ります。 AnimationControllerdrive関数にTweenを渡すか、 Tweenanimate関数にAnimationControllerを渡すことで、Animationが取得できます。

複数のTweenをつなげることもできます。例えばTweenATweenBをつなげると

元の値(0.0~1.0) → TweenAで変換した値 →TweenBで変換した値

のようになります。つまり、以下のコードは全て同じ挙動をします。ケースに分けて使い分けましょう。

...
Animation<Color> _animation;

@override
void initState() {
  ...
  final curvedAnimation = CurveTween(curve: Curves.bounceIn).animate(_controller);
  _animation = ColorTween(begin: Colors.blue, end: Colors.red,).animate(_controller);
}
...
Animation<Color> _animation;

@override
void initState() {
  ...
  _animation = _controller
      .drive(CurveTween(curve: Curves.bounceIn))
      .drive(ColorTween(begin: Colors.blue, end: Colors.red));
}
...
Animation<Color> _animation;

@override
void initState() {
  ...
  _animation = ColorTween(begin: Colors.blue, end: Colors.red)
      .chain(CurveTween(curve: Curves.bounceIn))
      .animate(_controller);
}

AnimationのWidgetへの割り当て

Animationの現在の値が_animation.valueで取得できるので、Widgetのパラメーターとして使います。

@override
Widget build(BuildContext context) {
  return Container(color: _animation.value)
}

しかし、これでは_animationの値が変化してもWidgetの色を変えることができないので、 AnimatedBuilderを使います。

@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
    animation: _animation,
    builder: (context, _) {
      return Container(color: _animation.value)
    },
  );
}

AnimatedBuilderanimation: に渡したAnimationの値が変わると builderで作られるWidgetが再描画されるWidgetです。 これを使うことで_animationの値に合わせてWidgetの色が変わるようになります。

また、AnimatedWidgetを継承してWidgetをつくることでも、値の変化に対応することが可能です。 AnimatedWidgetの場合も仕組みは同じで、super(listenable: )に渡したAnimationが更新される たびにbuild関数が再実行されます。

class ColorTransition extends AnimatedWidget {
  const ColorTransition({Animation color}): super(listenable: color);

  Animation<Color> get _color => listenable;

  @override
  Widget build(BuildContext context) {
    return Container(color: _color.value);
  }
}

Animated系WidgetとTransition系Widget

ここまではアニメーションをすべて自前で実装する手順を紹介していましたが、 デフォルトで用意されているWidgetを使うことで、もっと手軽にアニメーションを組むことができるようになります。

FlutterにはAnimatedContainerAnimatedThemeなど、Animated~という名前のWidgetと SlideTransitionFadeTransitionなどの~Transitionという名前のWidgetがあります。

これらはどちらもアニメーションを行うWidgetで、AnimatedPositionedPositionedTransitionのように 同じアニメーションをできるWidgetがそれぞれ用意されていたりするのでややこしいですが、 両者の違いは自前でAnimationControllerを持っているかどうかです。

Animated系Widgetは自前でAnimationControllerを持っているので、こうなってほしいという状態を渡すだけで 勝手にそこに向かって動いてくれます。

int _padding = 0;

@override
Widget build(BuildContext context) {

  return Stack(
    children: <Widget>[
      AnimatedPositioned(
        top: 0,
        left: _padding,
        width: 100,
        height: 100,
        duration: const Duration(milliseconds: 500),
        curve: Curves.easeInOut,
        child: ...,
      ),
    ],
  );
}

上のコードでは_paddingの値をsetState(() => { _padding = ... })で変えるだけで、 500ミリ秒かけてアニメーションをしてくれます。

一方Transition系Widgetは自前ではAnimationControllerを持っていないので、そとから渡してあげる必要があります。

@override
Widget build(BuildContext context) {
  return Stack(
    children: <Widget>[
      PositionedTransition(
        rect: _animationController.drive(
            RelativeRectTween(
              begin: RelativeRect.fromLTRB(0, 0, 100, 100),
              end: RelativeRect.fromLTRB(200, 0, 100, 100),
            ),
          ),
        child: ...,
      )
    ],
  );
}

Animated系WidgetはこちらでAnimationControllerを用意しなくていい分、より手軽に使うことができます。 一方こちらからAnimationControllerを渡せるTransitionは、アニメーションを途中で止めたり逆再生したりと Animated系Widgetではできなかったような柔軟な操作もできます。

目的のアニメーションがAnimated系にもTransition系にも用意されている場合は、まずAnimated系Widgetから検討してみて、 それでもうまくアニメーションが作れなかったときに、Transition系Widgetを試してみるのがいいと思います。

どのようなAnimated系WidgetやTransition系Widgetが用意されているかは、 monoさんの以下の記事を参考にしてください。

Flutterのお手軽にアニメーションを扱えるAnimated系Widgetをすべて紹介

https://link.medium.com/7471nO5Mi2

FlutterのTransition系アニメーションWidgetをすべて紹介

https://link.medium.com/qwcmc0aNi2

15日目: Flutterのアニメーションを理解する(前編) :

https://itome.team/blog/2019/12/flutter-advent-calendar-day15

17日目: FlutterのAnimatedWidgetを使いこなす :

https://itome.team/blog/2019/12/flutter-advent-calendar-day17