【Flutter】アニメーションAnimationControllerの使い方

※本記事は下記のZenn本にまとめました。

https://zenn.dev/cxj/books/07def04506182d

# はじめに
FlutterのAnimationControllerを用いて、アニメーションを実現する方法をまとめてみます。
今回は例を使ってAnimationControllerの使い方をまとめます。
※今記事のサンプルソースは[Github](https://github.com/ling350181/flutter_animation)にアップロードしています。

# AnimationController
## コンストラクター
|変数|型|デフォルト値|説明|
|---|---|---|---|
|value|double|null|アニメーションコントローラーの初期値|
|duration|Duration|null|アニメーションの長さ|
|reverseDuration|Duration|null|アニメーション逆再生時間|
|lowerBound|double|0.0|アニメーション下限|
|upperBound|double|1.0|アニメーション上限|
|animationBehavior|AnimationBehavior|normal|アニメーション動作|
|vsync|TickerProvider|required|Tickerプロバイダー|

## 初期値valueと長さduration
1. valueはコントローラーの初期値であり、下記の例では初期値は0.4から1.0まで変化する
1. durationはアニメーションの長さを表す、下記の例は500msと2000msで見比べて貰えば、一目瞭然

[/lib/value_duration/font_anim.dart](https://github.com/ling350181/flutter_animation/blob/main/lib/value_duration/font_anim.dart)
```dart
late AnimationController _ctrl;

  final Duration animDuration = const Duration(milliseconds: 500);

  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(
      value: 0.4,
      vsync: this,
      duration: animDuration,
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _startAnim,
      child: AnimatedBuilder(
        animation: _ctrl,
        builder: (_, __) {
          return Text(
            "Animation",
            style: TextStyle(
              color: Colors.blue,
              fontSize: fontSize,
            ),
          );
        },
      ),
    );
  }

  double get fontSize => 60 * _ctrl.value;

  void _startAnim() {
    _ctrl.reset();
    _ctrl.forward();
  }

```
|2000ms|500ms|
|---|---|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2491433/d56c7329-e311-fbe4-cf60-9df948c297f1.gif"width=240>|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2491433/a2f4bbef-38fc-9883-46b2-a2af8ec11a5e.gif"width=240>|

reverseDurationは逆再生時間を表す
ソースコードanimation_controller.dartの588行目あたり下記の記述がある
つまりreverseDurationを定義しないと逆再生できないとのこと
```dart
     final Duration directionDuration =
        (_direction == _AnimationDirection.reverse && reverseDuration != null)
        ? reverseDuration!
        : this.duration!;
```
下記の例ではreverseDurationを定義した上で_ctrl.reverse();を呼び出す
```dart
  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(
      value: 1,
      vsync: this,
      //duration: animDuration,
      lowerBound: 0.4,
      reverseDuration: animDuration,
    );
  }
...
  void _startAnim() {
    _ctrl.reverse();
  }
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2491433/a09f2fae-83ff-2cad-7d98-5a43390e5f3b.gif"width=240>

## lowerBoundとupperBound
lowerBoundとupperBoundを定義しなければ、コントローラーの値は0.0~1.0まで変化するが、
下記の例のように定義していれば、上下値は18~60となり、コントローラーの値は32~80まで変化する。
では、fontSizeは_ctrl.valueに設定して貰えば、fontSizeは18~60まで変化することとなる
```dart
  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(
      //value: 0.4,
      vsync: this,
      duration: animDuration,
      lowerBound: 18,
      upperBound: 60,
      //reverseDuration: animDuration,
    );
  }
  // フォントサイズ
  double get fontSize => _ctrl.value;
```

## animationBehaviorアニメーション動作
```dart
  /// The behavior of the controller when [AccessibilityFeatures.disableAnimations]
  /// is true.
  ///
  /// Defaults to [AnimationBehavior.normal] for the [new AnimationController]
  /// constructor, and [AnimationBehavior.preserve] for the
  /// [new AnimationController.unbounded] constructor.
  final AnimationBehavior animationBehavior;
```
列挙型の変数です。デフォルトはAnimationBehavior.normal
AnimationController.unboundedコンストラクターの場合はthis.animationBehavior = AnimationBehavior.preserve,となっている
```dart
enum AnimationBehavior {
  // [AccessibilityFeatures.disableAnimations]がtureの場合、duration時間を短縮する
  normal,
  // [AccessibilityFeatures.disableAnimations]がtureの場合、その動作を保持する
  preserve,
}
```

```dart
  AnimationController.unbounded({
    double value = 0.0,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    required TickerProvider vsync,
    this.animationBehavior = AnimationBehavior.preserve,
  }) : assert(value != null),
       assert(vsync != null),
       lowerBound = double.negativeInfinity,
       upperBound = double.infinity,
       _direction = _AnimationDirection.forward {
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value);
  }
```
VS CODEのデバッグでMediaQuery.of(context)で変数監視するとその値確認できる
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2491433/7d8340a9-2179-a021-adb8-a0cf2df1fdee.png"width=240>

## AnimationControllerの関数

1. forward
    アニメーションの開始メソッドであり、アニメーションコントローラーデフォルト値は0.0~1.0まで変化する。lowerBoundとupperBound設定すれば、値は[lowerBound〜upperBound]変化する
1. reverse
    アニメーションの逆生成メソッドであり、アニメーションコントローラーデフォルト値は1~0.0まで変化する。lowerBoundとupperBound設定すれば、値は[upperBound〜lowerBound]変化する
1. repeat
    パラメーター：
    ~~~
    1. min: アニメーション値の下限（デフォルトはアニメーションコントローラーの下限）
    2. max: アニメーション値の上限（デフォルトはアニメーションコントローラーの上限）
    3. reverse: アニメーション逆生成（デフォルトはfalse）
    4. duration: アニメーションの長さ（デフォルトはアニメーションコントローラーの長さ）
    ~~~
    reverseがfalseの場合、アニメーション値は[最小値〜最大値、最小値〜最大値...]の繰り返し
    reverseがtrueの場合は、アニメーション値は[最小値〜最大値、最大値〜最小値、最大値〜最小値...]の繰り返し
    [repeat](https://github.com/ling350181/flutter_animation/blob/main/lib/function/repeat/font_anim.dart)
    |false|true|
    |--|--|
    |<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2491433/a5d44ddf-06ef-1b87-7c1e-9ff93b5f1930.gif"width=240>|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2491433/a2f1dde7-fcab-0527-c39d-87037f5c856f.gif"width=240>|
1. reset
    resetはアニメーション値を下限値にリセットする
    ```dart
      void reset() {
        value = lowerBound;
      }
    ```
1. stop
    動画を停止する
1. fling
    言葉の通りです。アニメーションの値を急に増える感じです。
    下記の例で一目瞭然です。
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2491433/b11ff398-886f-3435-d1e2-e9f132de642a.gif"width=240>
    パラメーター
    |属性|型|デフォルト値|説明|
    |--|--|--|--|
    |velocity|double|1.0|速度|
    |springDescription|SpringDescription|null|スプリング パラメータの構成|
    |animationBehavior|AnimationBehavior|null|アニメーション動作|
    ```dart
     const SpringDescription({
        required this.mass, // 質量
        required this.stiffness,// 剛性
        required this.damping,// ダンピング
     });
    
     SpringDescription.withDampingRatio({
        required this.mass,
        required this.stiffness,
        double ratio = 1.0,
     }) : damping = ratio * 2.0 * math.sqrt(mass * stiffness);
    ```
    デフォルトは
    ```dart
    final SpringDescription _kFlingSpringDescription = SpringDescription.withDampingRatio(
      mass: 1.0,
      stiffness: 500.0,
      ratio: 1.0,
    );
    ```
    ダンピング大きいほどかかった時間が長い
    下記の例で動作を確認しましょう。
    [/lib/function/fling/line_anim.dart](https://github.com/ling350181/flutter_animation/blob/main/lib/function/fling/line_anim.dart)
    ```dart
    void _startAnim() async {
        _ctrl.reset();
        _ctrl.fling(
          velocity: 10,
          springDescription: SpringDescription.withDampingRatio(
            mass: 1.0,
            stiffness: 500.0,
            ratio: 5.0,
          ),
       );
    }
    ```
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2491433/911ddbe2-a533-250f-16ad-e1b2aef9ccf7.gif"width=240>
1. dispose
    _ticker オブジェクトを破棄する
1. animateTo
1. animateBack
1. animateWith

## AnimationControllerの状態監視
```dart
enum AnimationStatus {
  /// The animation is stopped at the beginning.
  dismissed,

  /// The animation is running from beginning to end.
  forward,

  /// The animation is running backwards, from end to beginning.
  reverse,

  /// The animation is stopped at the end.
  completed,
}
```
addStatusListenerで状態の変化を監視できる
```dart
_ctrl = AnimationController(
    vsync: this,
    duration: animDuration,
  )..addStatusListener(_listenStatus);
```