Getting Started With Staggered Animations in Flutter
Animations in mobile apps are powerful tools to attract users’ attention. They make transitions between screens and states smoother and more appealing for the user. In this tutorial, you’ll learn how to implement animations in Flutter. By Sébastien Bel.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Getting Started With Staggered Animations in Flutter
30 mins
- Getting Started
- Animating Goodnightmoonhellosun
- Using Implicit Animations
- Applying Tween Animations
- Implementing Curve Animations
- Animating the Sun and the Moon Implicitly
- Using Explicit Animations
- Animating With Animation
- AnimatedBuilder
- Coordinating Animations With Intervals
- Introducing AnimatedWidgets
- Implementing AnimatedWidget
- Animating Daytime and Nighttime Transition
- Animating the Theme
- Animating the TodayDetails Widget
- Interpolating a Custom Object
- Adding More Animations
- Add an Animation to BottomCard
- Make a Startup Animation
- Improve CloudyWidget
- Make It Rain or Snow
- Solution
- Where to Go From Here?
Animating the TodayDetails Widget
This widget is composed of two parts: The left one displays temperature, and the right one displays wind and weather type. The animation will take part in three phases:
- Move away from the original position and become transparent.
- Stay transparent away for a while.
- Move back to the original position and become visible.
In this case, you’ll need to use TweenSequence
, which allows you to separate a Tween
in several parts. Here’s an example:
TweenSequence<double>([
TweenSequenceItem(tween: Tween<double>(begin: 0.0, end: 0.5), weight: 4),
TweenSequenceItem(tween: ConstantTween<double>(0.5), weight: 2),
TweenSequenceItem(tween: Tween<double>(begin: 0.5, end: 1.0), weight: 4),
]);
Each TweenSequenceItem
has a weight used to determine its duration. For example, here you have a total weight of 10 with the following repartition:
- Go from 0.0 to 0.5 with a weight of 4, which is 4/10 of the total
TweenSequence
time. - Stay at 0.5 with a weight of 2, which is 2/10 of the total
TweenSequence
time. - Go from 0.5 to 1.0 with a weight of 4, which is 4/10 of the total
TweenSequence
time.
Interpolating a Custom Object
You need to animate two properties at once: Offset
for the movement and double
for the opacity. One way of doing it is to make a class that implements the operators used by Tween
for the interpolation. These are *
, +
and -
.
Create a new file, fade_away.dart, in the model directory:
import 'dart:ui';
class FadeAway {
final Offset offset;
final double opacity;
const FadeAway(this.offset, this.opacity);
FadeAway operator *(double multiplier) =>
FadeAway(offset * multiplier, opacity * multiplier);
FadeAway operator +(FadeAway other) =>
FadeAway(offset + other.offset, opacity + other.opacity);
FadeAway operator -(FadeAway other) =>
FadeAway(offset - other.offset, opacity - other.opacity);
}
FadeAway
implements all the mentioned operators to be able to animate it. You simply use Offset
and double
operators.
Go back to home_page.dart, import FadeAway
and add these new variables:
late TweenSequence<FadeAway> _temperatureAnim;
late TweenSequence<FadeAway> _weatherDetailsAnim;
You’ll use TweenSequence
since three phases make up the animation.
Init the TweenSequences
at the end of _initThemeAnims()
:
_temperatureAnim = TweenSequence<FadeAway>([
TweenSequenceItem(
tween: Tween<FadeAway>(
begin: const FadeAway(Offset(0, 0), 1.0),
end: const FadeAway(Offset(-100, 0), 0.0),
).chain(CurveTween(curve: Curves.easeInOut)),
weight: 40,
),
TweenSequenceItem(
tween: ConstantTween<FadeAway>(const FadeAway(Offset(-100, 0), 0.0)),
weight: 20,
),
TweenSequenceItem(
tween: Tween<FadeAway>(
begin: const FadeAway(Offset(-100, 0), 0.0),
end: const FadeAway(Offset(0, 0), 1.0),
).chain(CurveTween(curve: Curves.easeInOut)),
weight: 40,
),
]);
_weatherDetailsAnim = TweenSequence<FadeAway>([
TweenSequenceItem(
tween: Tween<FadeAway>(
begin: const FadeAway(Offset(0, 0), 1.0),
end: const FadeAway(Offset(100, 0), 0.0),
).chain(CurveTween(curve: Curves.easeInOut)),
weight: 40,
),
TweenSequenceItem(
tween: ConstantTween<FadeAway>(const FadeAway(Offset(100, 0), 0.0)),
weight: 20,
),
TweenSequenceItem(
tween: Tween<FadeAway>(
begin: const FadeAway(Offset(100, 0), 0.0),
end: const FadeAway(Offset(0, 0), 1.0),
).chain(CurveTween(curve: Curves.easeInOut)),
weight: 40,
),
]);
Each TweenSequence
has a different target position: Offset(-100, 0)
for _temperatureAnim
and Offset(100, 0)
for _temperatureAnim
. They move toward it, pause with ConstantTween
and finally come back to their origin.
Next, update the call to TodayDetailsWidget()
with:
TodayDetailsWidget(
weatherData: todayWeather,
progress: _animationController,
temperatureTween: _temperatureAnim,
detailsTween: _weatherDetailsAnim,
)
Just like SunWidget
and MoonWidget
, you’ll transform TodayDetailsWidget
into an AnimatedWidget
. Then, you’ll use your TweenSequence
and _animationController
to animate it.
Update TodayDetailsWidget
:
// ...
import '../model/fade_away.dart';
class TodayDetailsWidget extends AnimatedWidget {
final WeatherData weatherData;
final Animatable<FadeAway> temperatureTween;
final Animatable<FadeAway> detailsTween;
const TodayDetailsWidget({Key? key,
required this.weatherData,
required Animation<double> progress,
required this.temperatureTween,
required this.detailsTween})
: super(key: key, listenable: progress);
Animation<double> get _animation => listenable as Animation<double>;
@override
Widget build(BuildContext context) {
// 1
final temperatureCurrentValue = temperatureTween.evaluate(_animation);
final detailsCurrentValue = detailsTween.evaluate(_animation);
final now = DateTime.now();
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 2
Transform.translate(
offset: temperatureCurrentValue.offset,
child: Opacity(
child: _temperature(context, now),
opacity: temperatureCurrentValue.opacity,
),
),
const SizedBox(
width: 16,
),
// 3
Transform.translate(
offset: detailsCurrentValue.offset,
child: Opacity(
child: _windAndWeatherText(context, now),
opacity: detailsCurrentValue.opacity,
),
),
],
);
}
TodayDetailsWidget
is a bit different from your previous AnimatedWidget
s, especially in build()
:
- You use
_animation
‘s progress to evaluate eachTween
-interpolatedFadeAway
object instead of directly using the_animation
value in your widgets. - You move and fade the temperature using
temperatureCurrentValue
. - You do the same with
detailsCurrentValue
.
Also, notice that you declare temperatureTween
and detailsTween
as Animatable
instead of TweenSequence
. Both Tween
and TweenSequence
are Animatable
, so you can use any of them without any impact on TodayDetailsWidget
.
Remember that Tweens are not animated — only _animation
changes over time. Here, you get their interpolated value thanks to evaluate()
.
Hot restart and play the animation.
Congratulations! You’ve completed all the planned animations.
Adding More Animations
Use the next exercises to test your new skills.
Add an Animation to BottomCard
Try to animate BottomCard
by letting it rotate, bounce or scale when the theme changes, for instance.
Make a Startup Animation
You might need two AnimationController
for this: one for the day/night transition, one for the startup animation. In this case, don’t forget to change SingleTickerProviderStateMixin
to TickerProviderStateMixin
.
You may add the following in your initState()
:
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
// Do something on startup
});
It will launch at the startup of your widget, which is useful for a startup animation.
Improve CloudyWidget
Try to add some movements to the clouds, like you did for the sun and the moon. Here, consider using .repeat()
instead of forward()
on your AnimationController
. TweenSequence
is also a good fit for this animation.
Make It Rain or Snow
Since you know how to animate widgets, you can also simulate rain and snow. Make rectangles for the rain falling and white circles for the snow using Container
. Then, animate them with Tween
or TweenSequence
that you’ll repeat()
.
Solution
A complete solution is available in the challenges project of this tutorial’s resources.
Build and run, then watch the animations take place. :]
Where to Go From Here?
Download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.
This tutorial introduced you to staggered animations. After reviewing their main components, you can now chain many animations or make them overlap each other.
Check out the tutorial Flutter Canvas API: Getting Started to make even more custom animations. It teaches how to draw custom shapes, and even animate them!
You can also achieve great results with implicit animations. Learn how to do a radial menu in Implicit Animations in Flutter: Getting Started.
We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!