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
- AnimationController
- tween
- Curve
- 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),
)
Tween
- Define start and end point types and values
- Convert the value corresponding to the current progression to value other than
double
, such asOffset
,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,
);
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,
),
);
Animation
- The animation itself which will be binded to the widget you want to animate.
- Created by chaining
AnimationController
,Tween
andCurve
.
_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
- mixin
StatefulWidget
withSingleTickerProvider
. - pass the class itself to
AnimationController
- create a
Tween
to decide what value to convert to - If you want to change the curve of change, apply
Curve
. - Create
Animation
fromAnimationController
andTween
- bind it to a widget
- 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,
),
),
);
}
}
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 givenDuration
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
.
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.
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
Top comments (0)