【Flutter】Implicit animationsのサンプル集

本記事は[Flutter #2 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/flutter-2)の4日目の記事です。

## はじめに
Flutterのアニメーションを実装するとしたらさまざまなアプローチがありますが、その中でも**Implicit animations**は比較的楽に実装できます。
Implicit animationsを用いたアニメーションのサンプルを実装をしたので今回はその紹介をしたいと思います。

また紹介するアニメーションのサンプルは[こちらのリポジトリ](https://github.com/TsurumotoKentarou/Flutter-Implicit-Animations-Sample)で公開しています。

## Implicit Animationsとは
Implicit Animationsは日本語に直訳すると、暗黙的なアニメーションです。
Flutterのアニメーションには、暗黙的なアニメーションと明示的なアニメーションがあります。

暗黙的なアニメーションは、Widgetに対してアニメーションさせたいプロパティ(幅や高さや色など)の値を設定するだけで、**現在の値から新しい値までのアニメーションを処理してくれます。**
これらのウィジェットはまとめて暗黙のアニメーション、または暗黙的にアニメーション化されたウィジェットと呼ばれます。(これ以降では**Implicit Animations**と明記します)

対して明示的なアニメーションでは、AnimationControllerクラスを用いて実装する必要があります。
この記事では明示的なアニメーションの説明は割愛します。

## Implicit Animationsの種類
Implicit AnimationsのWidgetの多くは標準でフレームワークに組み込まれています。
Implicit AnimationsのWidgetのクラス名は、「AnimatedXXX」という名前で、「XXX」にはアニメーションされていないクラス名が入ることが多いです。
公式ドキュメントによると、Implicit Animationsには下記の種類があります。

* AnimatedAlign
* AnimatedContainer
* AnimatedDefaultTextStyle
* AnimatedOpacity
* AnimatedPadding
* AnimatedPhysicalModel
* AnimatedPositioned
* AnimatedPositionedDirectional
* AnimatedTheme
* AnimatedCrossFade
* AnimatedSize
* AnimatedSwitcher

それではそれぞれどんなアニメーションができるのか見ていきます。

## AnimatedAlign

AnimatedAlignは[Align]([Align class - widgets library - Dart API](https://api.flutter.dev/flutter/widgets/Align-class.html))のchildに設定したWidgetのalignmentをアニメーションさせることができるImplicit Animationsです。

下記のサンプルでは、FloatingActionButtonを押下すると、ContainerをCenterからBottom(またはその逆)へアニメーションさせています。

![animated_align.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/293742a7-3968-ac84-4628-42f41dbfdf59.gif)

```dart

class AnimatedAlignScreen extends StatefulWidget {
  @override
  _AnimatedAlignScreenState createState() => _AnimatedAlignScreenState();
}

class _AnimatedAlignScreenState extends State<AnimatedAlignScreen> {
  Alignment _alignment = Alignment.center;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedAlign Sample'),
      ),
      body: Center(
          child: AnimatedAlign(
        alignment: this._alignment,
        duration: Duration(seconds: 1),
        child: Container(color: Colors.red, width: 200, height: 200),
      )),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          setState(() {
            if (this._alignment == Alignment.center) {
              this._alignment = Alignment.bottomLeft;
            } else {
              this._alignment = Alignment.center;
            }
          });
        },
      ),
    );
  }
}
```

アニメーションの途中で停止したい場合など詳細に制御したい場合は、[AlignTransition]([AlignTransition class - widgets library - Dart API](https://api.flutter.dev/flutter/widgets/AlignTransition-class.html))クラスを使いましょう。

## AnimatedContainer
AnimatedContainerは[Container]([Container class - widgets library - Dart API](https://api.flutter.dev/flutter/widgets/Container-class.html))クラスのプロパティをアニメーションさせることができます。
アニメーションさせたいプロパティの値が変更されると、Durationの時間でアニメーションされます。

下記のサンプルではFloatingActionButtonを押下すると、1~300までのランダムな値をAnimatedContainerの幅と高さに設定して、アニメーションさせています。

![animated_container.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/6635fc8f-76bb-c18d-39d4-f34e4e392f8c.gif)


``` dart
class AnimatedContainerScreen extends StatefulWidget {
  @override
  _AnimatedContainerScreenState createState() =>
      _AnimatedContainerScreenState();
}

class _AnimatedContainerScreenState extends State<AnimatedContainerScreen> {
  double _width = 50;
  double _height = 50;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedContainer Sample'),
      ),
      body: Center(
        child: AnimatedContainer(
          color: Colors.red,
          width: this._width,
          height: this._height,
          duration: Duration(seconds: 1),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          setState(() {
            this._width = Random().nextInt(300).toDouble();
            this._height = Random().nextInt(300).toDouble();
          });
        },
      ),
    );
  }
}
```

プロパティにnullを設定するとアニメーションされません。
また、AnimatedContainerのchildプロパティに設定したWidgetにはアニメーションの影響はありません。

## AnimatedDefaultTextStyle
AnimatedDefaultTextStyleは[DefaultTextStyle]([DefaultTextStyle class - widgets library - Dart API](https://api.flutter.dev/flutter/widgets/DefaultTextStyle-class.html))クラスのImplicit Animationsです。

下記のサンプルでは、FloatingActionButtonを押下すると、TextのTextStyleが1秒かけてアニメーションされています。

![animated_default_text_style.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/743fed8e-214e-a5eb-3d42-4e26d4f08a63.gif)


``` dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class AnimatedDefaultTestStyleScreen extends StatefulWidget {
  @override
  _AnimatedDefaultTestStyleScreenState createState() =>
      _AnimatedDefaultTestStyleScreenState();
}

class _AnimatedDefaultTestStyleScreenState
    extends State<AnimatedDefaultTestStyleScreen> {
  TextStyle _textStyle1 =
      TextStyle(fontSize: 30, color: Colors.blue, fontWeight: FontWeight.w400);

  TextStyle _textStyle2 =
      TextStyle(fontSize: 15, color: Colors.green, fontWeight: FontWeight.bold);

  TextStyle _textStyle3 =
      TextStyle(fontSize: 24, color: Colors.red, fontWeight: FontWeight.w900);

  int _textStyleType = 0;

  TextStyle get _currentTextStyle {
    switch (this._textStyleType) {
      case 0:
        return this._textStyle1;
      case 1:
        return this._textStyle2;
      case 2:
        return this._textStyle3;
      default:
        return this._textStyle1;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedAlign Sample'),
      ),
      body: Center(
          child: AnimatedDefaultTextStyle(
        style: this._currentTextStyle,
        duration: Duration(seconds: 1),
        child: Center(
          child: const Text('TextStyleが変わります'),
        ),
      )),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          setState(() {
            int nextTextStyleType = this._textStyleType += 1;
            if (nextTextStyleType > 2) {
              nextTextStyleType = 0;
            }
            this._textStyleType = nextTextStyleType;
          });
        },
      ),
    );
  }
}
```

サンプルでは、TextStyleの**fontSize**、**color**、**fontWeight**をアニメーションさせていますが、下記のプロパティはアニメーションできず、設定したらすぐ変更されます。

* [textAlign](https://api.flutter.dev/flutter/widgets/AnimatedDefaultTextStyle/textAlign.html) 
* [softWrap](https://api.flutter.dev/flutter/widgets/AnimatedDefaultTextStyle/softWrap.html) 
* [overflow](https://api.flutter.dev/flutter/widgets/AnimatedDefaultTextStyle/overflow.html) 
* [maxLines](https://api.flutter.dev/flutter/widgets/AnimatedDefaultTextStyle/maxLines.html)
* [textWidthBasis](https://api.flutter.dev/flutter/widgets/AnimatedDefaultTextStyle/textWidthBasis.html)
* [textHeightBehavior](https://api.flutter.dev/flutter/widgets/AnimatedDefaultTextStyle/textHeightBehavior.html) 

アニメーションの途中で停止したい場合など詳細に制御したい場合は、[DefaultTextStyleTransition](https://api.flutter.dev/flutter/widgets/DefaultTextStyleTransition-class.html)クラスを使いましょう。

## AnimatedOpacity
AnimatedOpacityは [Opacity](https://api.flutter.dev/flutter/widgets/Opacity-class.html) のImplicit Animationsです。Widgetの透明度をアニメーションさせることができます。
下記のサンプルではFloatingActionButtonを押下すると、ContainerのOpacityを1.0から0.0(またはその逆)にアニメーションさせています。

![animated_opacity.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/ab004a64-3f4d-3cb4-cac1-2f2497877414.gif)

``` dart
class AnimatedOpacityScreen extends StatefulWidget {
  @override
  _AnimatedOpacityScreenState createState() => _AnimatedOpacityScreenState();
}

class _AnimatedOpacityScreenState extends State<AnimatedOpacityScreen> {
  double _opacity = 1.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedOpacity Sample'),
      ),
      body: Center(
          child: AnimatedOpacity(
        opacity: this._opacity,
        duration: Duration(seconds: 1),
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
        ),
      )),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          setState(() {
            this._opacity = (this._opacity >= 1.0) ? 0.0 : 1.0;
          });
        },
      ),
    );
  }
}
```

**また、透明度のアニメーションは処理が重いため必要最小限に留めておきましょう。**
(下記[公式ドキュメント]([AnimatedOpacity class - widgets library - Dart API](https://api.flutter.dev/flutter/widgets/AnimatedOpacity-class.html))の引用)

```
Animating an opacity is relatively expensive because it requires painting the child into an intermediate buffer.
```

## AnimatedPadding
AnimatedPaddingは [Padding](https://api.flutter.dev/flutter/widgets/Padding-class.html) クラスのImplicit Animationsです。WidgetのPaddingをアニメーションさせることができます。
下記のサンプルではFloatingActionButtonを押下すると、ContainerのPaddingにランダム値を設定しアニメーションさせています。

![animated_padding.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/688ea7e2-cbc8-98cc-a40f-bcd20835e425.gif)


``` dart
import 'dart:math';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class AnimatedPaddingScreen extends StatefulWidget {
  @override
  _AnimatedPaddingScreenState createState() => _AnimatedPaddingScreenState();
}

class _AnimatedPaddingScreenState extends State<AnimatedPaddingScreen> {
  EdgeInsets _edgeInsets = EdgeInsets.all(20);

  double _randomPaddingValue() {
    return Random().nextInt(200).toDouble();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedPadding Sample'),
      ),
      body: Center(
          child: AnimatedPadding(
        padding: this._edgeInsets,
        duration: Duration(seconds: 1),
        child: Container(color: Colors.red),
      )),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          setState(() {
            final Random random = Random();
            double top = this._randomPaddingValue();
            double right = this._randomPaddingValue();
            double left = this._randomPaddingValue();
            double bottom = this._randomPaddingValue();
            this._edgeInsets = EdgeInsets.only(
                top: top, right: right, left: left, bottom: bottom);
          });
        },
      ),
    );
  }
}
```

## AnimatedPhysicalModel
AnimatedPhysicalModelは [PhysicalModel](https://api.flutter.dev/flutter/widgets/PhysicalModel-class.html) のImplicit Animationsです。
PhysicalModelクラスは**child**プロパティに設定したWidgetに対してZ座標の概念を持たせることができます。
Z座標の概念を持ったWidgetは影が描画され、影の色や形などを設定できます。

下記のサンプルではFloatingActionButtonを押下すると、AnimatedPhysicalModelを使用して、Z座標の値、Containerの色、影の色をアニメーションさせています。
(影の形を表すAnimatedPhysicalModelの**shape**プロパティはアニメーションできないので固定値となっています)

![animated_physical_model.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/34ec9bfb-72bf-77b0-7da0-752334c794d5.gif)

``` dart
class AnimatedPhysicalModelScreen extends StatefulWidget {
  @override
  _AnimatedPhysicalModelScreenState createState() =>
      _AnimatedPhysicalModelScreenState();
}

class _AnimatedPhysicalModelScreenState
    extends State<AnimatedPhysicalModelScreen> {
  // Z座標
  static final double fromElevation = 10.0;
  static final double toElevation = 30.0;
  double _elevation = fromElevation;

  // 色
  static final Color fromColor = Colors.orange;
  static final Color toColor = Colors.greenAccent;
  Color _color = fromColor;

  // 影の色
  static final Color fromShadowColor = Colors.red;
  static final Color toShadowColor = Colors.green;
  Color _shadowColor = fromShadowColor;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedPhysicalModel Sample'),
      ),
      body: Center(
          child: AnimatedPhysicalModel(
        elevation: this._elevation,
        shape: BoxShape.rectangle,
        color: this._color,
        shadowColor: this._shadowColor,
        duration: Duration(seconds: 1),
        child: Container(
          width: 200,
          height: 200,
        ),
      )),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          setState(() {
            if (this._elevation >= toElevation) {
              this._elevation = fromElevation;
              this._color = fromColor;
              this._shadowColor = fromShadowColor;
            } else {
              this._elevation = toElevation;
              this._color = toColor;
              this._shadowColor = toShadowColor;
            }
          });
        },
      ),
    );
  }
}
```

## AnimatedPositioned
AnimatedPositionedは [Positioned](https://api.flutter.dev/flutter/widgets/Positioned-class.html) クラスのImplicit Animationsです。Positionedクラスは[Stack]([Stack class - widgets library - Dart API]
(https://api.flutter.dev/flutter/widgets/Stack-class.html))クラスの**child**プロパティに設定することで動作します。
下記のサンプルではFloatingActionButtonを押下すると、AnimatedPositionedの**top**、**right**、**left**、**bottom**の値を設定し、**child**プロパティに設定しているContainerの位置をアニメーションさせています。

![animated_position.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/9b00e590-9a23-cc3b-8225-dcb415cfa6cd.gif)


``` dart
class AnimatedPositionedScreen extends StatefulWidget {
  @override
  _AnimatedPositionedScreenState createState() =>
      _AnimatedPositionedScreenState();
}

class _AnimatedPositionedScreenState extends State<AnimatedPositionedScreen> {
  // top
  static final double _fromTop = 50;
  static final double _toTop = 100;
  double _top = _fromTop;

  // right
  static final double _fromRight = 150;
  static final double _toRight = 60;
  double _right = _fromRight;

  // left
  static final double _fromLeft = 0;
  static final double _toLeft = 60;
  double _left = _fromLeft;

  // bottom
  static final double _fromBottom = 50;
  static final double _toBottom = 100;
  double _bottom = _fromBottom;

  // fromとtoのフラグ
  bool isFrom = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedPositioned Sample'),
      ),
      body: Center(
        child: Stack(
          children: [
            Positioned(
                child: Container(
              width: 400,
              height: 400,
              color: Colors.red,
            )),
            AnimatedPositioned(
                duration: Duration(seconds: 1),
                top: this._top,
                right: this._right,
                left: this._left,
                bottom: this._bottom,
                child: Container(
                  color: Colors.green,
                ))
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          setState(() {
            this.isFrom = !this.isFrom;
            this._top = this.isFrom ? _fromTop : _toTop;
            this._right = this.isFrom ? _fromRight : _toRight;
            this._left = this.isFrom ? _fromLeft : _toLeft;
            this._bottom = this.isFrom ? _fromBottom : _toBottom;
          });
        },
      ),
    );
  }
}
```

こちらのサンプルでは、**top**、**right**、**left**、**bottom**を変化させることで、Containerのサイズが変更しています。そのようなケースではAnimatedPositionedが適しています。
もし、Containerのサイズを同じままにして、**位置のみ**を変更する場合は、 [SlideTransition](https://api.flutter.dev/flutter/widgets/SlideTransition-class.html) を検討した方が良いです。

## AnimatedPositionedDirectional

AnimatedPositionedDirectionalは[PositionedDirectional](https://api.flutter.dev/flutter/widgets/PositionedDirectional-class.html)のImplicit Animationsです。
PositionedDirectionalはPositionedクラスとほとんど同じです。
違いは**Widgetの横方向の始点と終点を変更できる点です。**
例えば日本語のテキストだと、文字は**左から右へ**流れていきますが、言語によっては**右から左へ**流れる言語もあります。
**条件によってどっちの方向が始点でどっちの方向が終点なのかが変わる場合**はPositionedではなく、PositionedDirectionalを使用します。

下記のサンプルでは、上記で紹介したAnimatedPositionedのサンプルを**右方向を始点**にしてアニメーションさせています。
(AnimatedPositionedの部分をAnimatedPositionedDirectionalに変更したコード以外は同じなので省略します。)

![animated_position_directional.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/3400bed8-4321-be99-065d-1ee07fa3ef13.gif)


``` dart

Directionality(
    textDirection: TextDirection.rtl,	// 右を始点にする
    child: AnimatedPositionedDirectional(
        duration: Duration(seconds: 1),
        top: this._top,
        start: this._left,				// leftからstartに変更
        end: this._right,				// rightからendに変更
        bottom: this._bottom,
        child: Container(
          color: Colors.green,
        )))
```

## AnimatedTheme
AnimatedThemeは [Theme](https://api.flutter.dev/flutter/material/Theme-class.html) クラスのImplicit Animationsです。
Themeクラスは、**child**プロパティに設定したWidgetに [ThemeData](https://api.flutter.dev/flutter/material/ThemeData-class.html)  データを適用できます。
ThemeDataではColorやタイポグラフィなどを設定でき、これらのデータをAnimatedThemeを使ってアニメーションさせます。

下記のサンプルでは、FloatingActionButtonを押下すると、Cardの背景色をライトモードとダークモードで切り替えるようにアニメーションしています。

![animated_theme.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/94ff51ef-44bc-d68a-354c-dbf76b37c4b2.gif)

まず、MaterialAppでライトモードとダークモードのThemeDataを設定する必要があります。

``` main.dart
class MyApp extends StatelessWidget {
  static final ThemeData _lightTheme = ThemeData(
      primaryColor: Colors.blue,
      accentColor: Colors.blue,
      textTheme: TextTheme(bodyText2: TextStyle(color: Colors.black)));

  static final ThemeData _darkTheme = ThemeData(
      primaryColor: Colors.blue,
      accentColor: Colors.black,
      textTheme: TextTheme(bodyText2: TextStyle(color: Colors.white)));

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '',
      theme: _lightTheme,
      darkTheme: _darkTheme,
    );
  }
}
```

AnimatedThemeで設定したThemeDataを参照することで、**child**プロパティのWidgetに反映されます。

``` animated_theme_screen.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class AnimatedThemeScreen extends StatefulWidget {
  @override
  _AnimatedThemeScreenState createState() => _AnimatedThemeScreenState();
}

class _AnimatedThemeScreenState extends State<AnimatedThemeScreen> {
  bool _isLightMode = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedTheme Sample'),
      ),
      body: Center(
          child: AnimatedTheme(
        duration: Duration(seconds: 1),
        data: this._isLightMode ? ThemeData.light() : ThemeData.dark(),
        child: Card(
          child: Padding(
            padding: EdgeInsets.all(50),
            child: Text(
              this._isLightMode ? 'ライトモード' : 'ダークモード',
              style: TextStyle(fontSize: 30),
            ),
          ),
        ),
      )),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          setState(() {
            this._isLightMode = !this._isLightMode;
          });
        },
      ),
    );
  }
}
```

## AnimatedCrossFade
AnimatedCrossFadeは異なる二つのWidgetをクロスフェードアニメーションさせるImplicit Animationsです。

下記のサンプルでは色とサイズの違うContainerをクロスフェードさせています。

![animated_crossfade.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/70fb3ed2-bbae-4088-1441-d28aa987c21f.gif)

``` dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class AnimatedCrossFadeScreen extends StatefulWidget {
  @override
  _AnimatedCrossFadeScreenState createState() =>
      _AnimatedCrossFadeScreenState();
}

class _AnimatedCrossFadeScreenState extends State<AnimatedCrossFadeScreen> {
  // 最初に表示するWidget
  Widget _firstWidget() {
    return Container(
      width: 400,
      height: 200,
      color: Colors.red,
    );
  }

  // クロスフェードするWidget
  Widget _secondWidget() {
    return Container(
      width: 200,
      height: 300,
      color: Colors.blue,
    );
  }

  // 最初のWidgetを表示するフラグ
  bool showFirst = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedCrossFade Sample'),
      ),
      body: Center(
          child: AnimatedCrossFade(
        duration: Duration(seconds: 1),
        crossFadeState: this.showFirst
            ? CrossFadeState.showFirst
            : CrossFadeState.showSecond,
        firstChild: this._firstWidget(),
        secondChild: this._secondWidget(),
        // layoutBuilderでサイズの違うWidgetでも違和感なく表示する
        layoutBuilder: (topChild, topChildKey, bottomChild, bottomChildKey) {
          return Stack(
            overflow: Overflow.visible,
            alignment: Alignment.center,
            children: [
              Positioned(
                child: bottomChild,
                key: bottomChildKey,
              ),
              Positioned(
                child: topChild,
                key: topChildKey,
              )
            ],
          );
        },
      )),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          setState(() {
            this.showFirst = !this.showFirst;
          });
        },
      ),
    );
  }
}
```

AnimatedCrossFadeは同じ幅のWidgetをクロスフェードするのを目的としています。
異なる幅をクロスフェードさせようした場合は、クロスフェードさせようとしたWidgetが急に現れる(伝えづらい…)ような違和感ある動きになってしまいます。
その場合、**layoutBuilder**を使用すると違和感なく表示することができます。
詳しくは[公式ドキュメント]([AnimatedCrossFade class - widgets library - Dart API](https://api.flutter.dev/flutter/widgets/AnimatedCrossFade-class.html))を参照ください。

## AnimatedSize
AnimatedSizeは**child**に設定したWidgetのサイズをアニメーションできるImplicit Animationsです。

下記のサンプルではFlutterロゴのサイズを変更するアニメーションをします。

![animated_size.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/22f3ee37-b623-041a-4b06-0002954a9d08.gif)

``` dart
import 'dart:math';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class AnimatedSizeScreen extends StatefulWidget {
  @override
  _AnimatedSizeScreenState createState() => _AnimatedSizeScreenState();
}

class _AnimatedSizeScreenState extends State<AnimatedSizeScreen>
    with SingleTickerProviderStateMixin {
  double _size = 200;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedSize Sample'),
      ),
      body: Center(
          child: Container(
        color: Colors.amberAccent,
        child: AnimatedSize(
            vsync: this,
            duration: Duration(seconds: 1),
            child: FlutterLogo(
              size: this._size,
            )),
      )),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          setState(() {
            this._size = Random().nextInt(300).toDouble();
          });
        },
      ),
    );
  }
}
```

**※ childプロパティにFlutterLogoではなくContainerを指定したところ、アニメーションが滑らかにいきませんでした。こちらは調査中です。**

## AnimatedSwitcher

AnimatedSwitcherは**child**プロパティに新たに設定したWidgetと、前のWidgetをアニメーションで切り替えるImplicit Animationsです。

デフォルトのアニメーションはクロスフェードですが、そのほかにもスケールしながら表示させる[**ScaleTransition**]([ScaleTransition class - widgets library - Dart API](https://api.flutter.dev/flutter/widgets/ScaleTransition-class.html))や、回転しながら表示させる[**RotationTransition**]([RotationTransition class - widgets library - Dart API](https://api.flutter.dev/flutter/widgets/RotationTransition-class.html))なども設定できます。

注意点として、**新たに表示させるWidgetと前のwidgetが同じWidgetの場合は、keyを設定する必要があります。**

下記のサンプルでは、ElevatedButton押下でカウントをインクリメントされたテキストをScaleTransitionでアニメーションさせています。

![animated_switcher.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/0a008408-8942-173f-6be4-a80524b7e05c.gif)

``` dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class AnimatedSwitcherScreen extends StatefulWidget {
  @override
  _AnimatedSwitcherScreenState createState() => _AnimatedSwitcherScreenState();
}

class _AnimatedSwitcherScreenState extends State<AnimatedSwitcherScreen> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedSize Sample'),
      ),
      body: Center(
        child: Container(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              AnimatedSwitcher(
                duration: const Duration(milliseconds: 500),
                transitionBuilder: (Widget child, Animation<double> animation) {
                  return ScaleTransition(child: child, scale: animation);
                },
                child: Text(
                  '$_count',
                  key: ValueKey<int>(_count),
                  style: Theme.of(context).textTheme.headline4,
                ),
              ),
              ElevatedButton(
                child: const Text('Increment'),
                onPressed: () {
                  setState(() {
                    this._count += 1;
                  });
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}
```

## 終わりに
本記事ではImplicit Animationsのサンプルを紹介しました。

各アニメーションの実装で省略しましたが、ほとんどのImplicit Animationsには[Curve](https://api.flutter.dev/flutter/animation/Curve-class.html)を設定でき、アニメーションの速度の波を変えることもできます。

またこちらも紹介しませんでしたが、[TweenAnimationBuilder](https://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html)クラスを使うと、カスタムのアニメーションを実装できるので、これまで紹介したImplicit Animationsでは実現が難しいならこちらを検討するのも良いと思います。

以上です。

## 参考にしたサイト
* [Implicit animations  - Flutter](https://flutter.dev/docs/development/ui/animations/implicit-animations)
* [ImplicitlyAnimatedWidget class - widgets library - Dart API](https://api.flutter.dev/flutter/widgets/ImplicitlyAnimatedWidget-class.html)
* [Flutterのお手軽にアニメーションを扱えるAnimated系Widgetをすべて紹介](https://medium.com/flutter-jp/implicit-animation-b9d4b7358c28)

## リポジトリ
https://github.com/TsurumotoKentarou/Flutter-Implicit-Animations-Sample