【Flutter】Mastering Animation ~1. Basic~
Tags: flutter, codenewbie, dart
Published: 2022-11-12 13:38:25

This article is the first in a series of articles on animation.

In the development of an application, although UI is a must, priority of animation are often kept lower and left out until all other developments are finished. If time and budget is short, it will likely not be implemented. 

However, by adding effective animations, the impression of the app will increase dramatically even with the same functionality. And sometimes contributes greatly on the user engagement for the app.

In this article, we will discuss the basics and applications of animation in multiple articles.
# Basics of Animation
There are four main players (classes and widgets) in the animation
1. AnimationController
2. tween
3. Curve
4. animation

In addition to these classes, many other classes are involved, but these four classes are the ones that must be implemented when implementing animation.

You can not go without them when implementing animations.

## AnimationController
- Manage animation progression with a `double` type from 0.0 to 1.0.
- Responsible for operations such as play, stop and reverse play within this value
- brake and accelerator for animation

```dart
_controller = AnimationController(
     vsync: this,
     duration: const Duration(milliseconds: 1000),
   )
```

## Tween
- Define start and end point types and values
- Convert the value corresponding to the current progression to value other than `double`, such as `Offset`, `Color`, etc., depending on the animation you want
- Converter for current value that correspond with current progression.

```dart
   _tween = Tween<Offset>(
     begin: const Offset(0, -1000),
     end: Offset.zero,
   );
```

## Curve
- Tween-transformed values change linearly by default
- `Curve` adds a transformation on the changing curves
- Is Applied to `Tween`
- You do not need to use `Curve` if you prefer linear changes.

```dart
   _tween = Tween<Alignment>(
     begin: Alignment.topLeft,
     end: Alignment.bottomRight,
   ).chain(
     CurveTween(
       curve: Curves.fastLinearToSlowEaseIn,
     ),
   );
```

## Animation
- The animation itself which will be binded to the widget you want to animate.
- Created by chaining `AnimationController`, `Tween` and `Curve`.

```dart
   _animation = AnimationController(
     vsync: this,
     duration: const    
       Duration(milliseconds: 1000),
   ).drive(
     Tween<Alignment>(
       begin: Alignment.topLeft,
       end: Alignment.bottomRight,
     ).chain(
       CurveTween(
         curve:         
       Curves.fastLinearToSlowEaseIn,
       ),
     ),
   );

```
in short,
## `AnimationController` x `Tween` (x `Curve`) = `Animation`!!!


# implementing Animation
Animation is implemented through following steps
1. mixin `StatefulWidget` with `SingleTickerProvider`.
2. pass the class itself to `AnimationController`
3. create a `Tween` to decide what value to convert to
4. If you want to change the curve of change, apply `Curve`. 
5. Create `Animation` from `AnimationController` and `Tween`
6. bind it to a widget
7. forward it at any timing

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

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { // <<< 1. mixin SingleTickerProviderStateMixin to StatefulWidget
  late AnimationController controller;
  late Tween<Alignment> tween;
  final Curve curve = Curves.ease;
  late Animation<Alignment> animation;

  @override
  void initState() {
    controller = AnimationController(duration: Duration(seconds: 3),vsync: this); // <<< 2. Pass this class to AnimationController
    tween = Tween(begin: Alignment.topCenter,end: Alignment.bottomCenter); // <<< 3. Decide animate what value and it's start and end values.
    tween.chain(CurveTween(curve:curve)); // <<< 4. Apply curve effect to Tween
    animation = controller.drive(tween); // <<< 5. Create Animation by AnimationController x Tween
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter app'),
      ),
      body: AnimatedBuilder( 
        animation: animation,
        builder: (context, _){
          return Align(
            alignment: animation.value, // <<< 6. Apply Animation to Widget
            child: Text('Hello world!'),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          controller.forward(); // <<< 7. Start Animating by forward!!
        },
        backgroundColor: Colors.yellow[700],
        child: Icon(
          Icons.bolt,
          color: Colors.black,
        ),
      ),
    );
  }
}
```


# A little explanation
## What is `SingleTickerProvider`?
- It passes a `Ticker` class to the Widget that tells the device-specific frame rate.
- `Ticker` tells you how often to redraw the Widget in a given `Duration`

```dart
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {

  _controller = AnimationController(
     vsync: this,
     duration: const Duration(milliseconds: 500),
   );
```

## Binding an Animation to a Widget
There are two ways to bind an Animation to a Widget.
1) Wrap the Widget in an `AnimatedBuilder`.
2) Cut it out into custom widget that inherits `AnimatedWidget`.

### AnimatedBuilder
Since `AnimatedBuilder` inherits from `AnimatedWidget` behind the scenes, what it actually does is the same, but by wrapping the widget, you can animate widgets without cutting it out as a widget.

`animation` parameter defines the class that will notify when to redraw the widget, and can be either an `Animation` or the `AnimationController` used to create it.

This is because `AnimationController` receives the redraw timing from `TickerProvider` via `vsync:this`.

```dart
AnimatedBuilder( 
        animation: animation, // <<< can be AnimationController as well
        builder: (context, _){
          return Align(
            alignment: animation.value, // <<< 6. bind Animation to the widget you want to animate
            child: Text('Hello world!'),
          );
        },
      )
```

### AnimatedWidget
On the other hand, by preparing a customi widget that inherits `AnimatedWidget`, widget can be cut out. If you want to use an animated widget repeatedly, you can use this method.

As with the `animation` parameter of the `AnimatedBuilder`, the `listenable` parameter is provided to notify when to redraw the widget.

With `AnimatedWidget`, you have to pass `Animation` to `listenable` parameter and not `AnimationController` because you can only access the argument through getter.

```dart
class AnimatingText extends AnimatedWidget {
  const AnimatingText({
    super.key,
    required Animation<Alignment> animation,
  }) : super(listenable: animation);

  Animation<Alignment> get _alignment => listenable as Animation<Alignment>; // <<< arguments can only be accessed through getter after passing it to listenable parameter

  @override
  Widget build(BuildContext context) {
    return Align(
        alignment: _aligment.value, // <<< arguments can't be passed directly
        child: Text('Hello world!'),
    );
  }
}
```

With either approach, `Animation.value` tells the Widget the current value.

By wrapping or cutting out the widget you want to animate in either of these ways, you can tell widgets to redraw at the timing of the `Animation` you passed.

### Sample Repository

https://github.com/heyhey1028/flutter_samples/tree/main/samples/master_animation

# What's next?

This is the basics of animation. Next, let's try a more complex animation.

https://dev.to/heyhey1028/flutter-mastering-animation-2-multiple-effects-and-tweensequence-3okn

### Reference
- https://itome.team/blog/2019/12/flutter-advent-calendar-day15/ (ja)
- https://itome.team/blog/2019/12/flutter-advent-calendar-day16/ (ja)