DEV Community

Cover image for 【Flutter】Mastering Animation ~1. Basic~
heyhey1028💙🔥
heyhey1028💙🔥

Posted on

【Flutter】Mastering Animation ~1. Basic~

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
_controller = AnimationController(
     vsync: this,
     duration: const Duration(milliseconds: 1000),
   )
Enter fullscreen mode Exit fullscreen mode

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.
   _tween = Tween<Offset>(
     begin: const Offset(0, -1000),
     end: Offset.zero,
   );
Enter fullscreen mode Exit fullscreen mode

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.
   _tween = Tween<Alignment>(
     begin: Alignment.topLeft,
     end: Alignment.bottomRight,
   ).chain(
     CurveTween(
       curve: Curves.fastLinearToSlowEaseIn,
     ),
   );
Enter fullscreen mode Exit fullscreen mode

Animation

  • The animation itself which will be binded to the widget you want to animate.
  • Created by chaining AnimationController, Tween and Curve.
   _animation = AnimationController(
     vsync: this,
     duration: const    
       Duration(milliseconds: 1000),
   ).drive(
     Tween<Alignment>(
       begin: Alignment.topLeft,
       end: Alignment.bottomRight,
     ).chain(
       CurveTween(
         curve:         
       Curves.fastLinearToSlowEaseIn,
       ),
     ),
   );

Enter fullscreen mode Exit fullscreen mode

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
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,
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {

  _controller = AnimationController(
     vsync: this,
     duration: const Duration(milliseconds: 500),
   );
Enter fullscreen mode Exit fullscreen mode

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.

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!'),
          );
        },
      )
Enter fullscreen mode Exit fullscreen mode

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.

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!'),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)