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.

Leave a rating/review
Download materials
Save for later
Share

If you want your app to stand out from the crowd, you might be surprised at what a difference just a few animations make. Not only can animations bring your app to life, but they’re also a very simple way to explain what’s happening on the screen.

Luckily, Flutter makes the process of adding animations very easy. You can use implicit animations for simple cases, or you can choose explicit animations if you need more control.

Implicit animations are really easy to use, but they only allow you to control the duration and the curve of your animations. For example, use AnimatedFoo instead of Foo and your widget will animate automatically when its values change — and Container becomes AnimatedContainer. Find the full list for ImplicitlyAnimatedWidget in the Flutter documentation.

When you need more control over the lifecycle of your animation, though, such as the ability to pause it or launch it on demand, you have to use explicit animations. They’re especially useful when there are several elements to coordinate.

In this tutorial, you’ll build a weather app called Good Night Moon Hello Sun. With the help of staggered animations, you’ll create a custom day-to-night animation using:

  • AnimationController
  • Tween
  • Tweens with Intervals
  • AnimatedBuilder and AnimatedWidget to render the animations

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial and open it in your favorite IDE.

Note: This tutorial uses Android Studio, but Visual Studio Code works fine, too. Similarly, this tutorial uses Chrome as the test device, but you could also run it on mobile.

Starter project directories and files

The code is in the lib folder, and the images are in assets/icons. Open main.dart and take a look at the home widget given to MaterialApp:

LayoutBuilder(builder: (context, constraints) {
  return HomePage(width: constraints.maxWidth);
})

HomePage uses LayoutBuilder constraints to know its size and be more responsive than with hard-coded values.

Speaking of which, open home_page.dart.

Since you’re building a weather app, you need to give it some data. SampleDataGenerator generates weather mock data using classes you’ll find in model:

  • WeatherData: Weather for a day with a list of its details.
  • WeatherDataDetails: Weather, temperature and wind at a given time of the day.

You usually change the theme of an app by changing MaterialApp‘s theme, but MaterialApp includes a default implicit animation that’s not customizable. So instead, you’ll use the Theme widget — which is not animated by default — to fully control the animation between the day and night themes.

In home_page.dart, build() only contains Theme, which depends on _isDayTheme because _content() returns the rest of the widget tree. You’ll use _animationButton() to launch the animation.

The rest of the widgets are pretty common, except for these custom ones:

  • SunWidget
  • MoonWidget
  • CloudyWidget
  • TodayDetailsWidget
  • BottomCard

Take a look at sun_widget.dart. This is your sun, which you’ll animate later. Note, again, the presence of LayoutBuilder to set its size.

Next, open moon_widget.dart. You’ll see that MoonWidget uses an image instead of just a Container.

cloudy_widget.dart shows clouds on top of SunWidget or MoonWidget, depending on the theme.

today_details_widget.dart is where you display the temperature, wind and weather of the day.

Finally, open bottom_card.dart. BottomCard displays both the hourly weather and the forecast for the next five days.

Now that you’re familiar with the project’s code, build and run.

Test switching the theme by clicking SWITCH THEMES; you’ll notice you have two different themes for your app, but no animations yet.

The weather app without animations

Animating Goodnightmoonhellosun

The transition from day to night includes several elements.

Planned animations

The scheme above illustrates the transition. First, the sun disappears by moving away. Then, the moon replaces it by moving in.

Other elements animate in the middle of this transition:

  • Theme: Changes from the day theme to the night theme with a fade in/fade out transition of its colors.
  • TodayDetailsWidget: Moves and fades at the same time.

The night-to-day transition is the same, but you swap the sun and moon while the theme transition goes from the night theme to the day theme. Some animations run one after the other, while others animate in parallel.

This is how the animation will look when you finish this tutorial:

Final animation

Using Implicit Animations

You’ll start by animating the sun and the moon during the day-to-night transition. The sun moves to the left, and the moon appears from the right.

You have several options to do this using implicitly animated widgets, such as AnimatedSlide and TweenAnimationBuilder. Here, you’ll use the latter, which needs a Tween.

Applying Tween Animations

Tween defines the starting and ending points of your animations, as its definition suggests:

Tween<T>(begin: T, end: T)

To interpolate between 0.0 and 1.0, for instance, you’d write:

Tween<double>(begin: 0.0, end: 1.0)

It works with many objects, and you can even try it with Colors!

Tween interpolates between two values using the operators +, and * in its lerp method. This means you can make Tween between custom objects if you implement these. It also means some objects can’t interpolate well since they don’t do it or their operators don’t fit. For example, the operator * in the int class returns num instead of int, which is the reason why Tween can’t interpolate it.

There are a few prebuilt Tweens for these cases. For example, you can use IntTween for int or ConstantTween, which is a Tween that stays at the same value. See the implementers of Tween for the full list.

Implementing Curve Animations

Instead of playing your animations linearly, you can apply different curves to them. Curves includes the most common ones, like easeIn and easeOut. See them animated in the docs.

There are several ways to apply a curve to your animations. One is to apply the curve directly to a Tween by calling chain() on it:

Tween<double>(begin: 0.0, end: 1.0)
    .chain(CurveTween(curve: Curves.slowMiddle));

This Tween would have a slowMiddle curve.

Animating the Sun and the Moon Implicitly

To see how implicit animations work and what their limitations are, you’ll try animating the sun and moon implicitly first.

Replace the contents of _sunOrMoon() in lib/ui/home_page.dart with the following:

return Stack(children: [
  // TweenAnimationBuilder for the sun
  TweenAnimationBuilder<Offset>(
    duration: const Duration(seconds: 2),
    curve: Curves.bounceIn,
    tween: Tween<Offset>(
        begin: const Offset(0, 0), end: const Offset(-500, 0)),
    child: const SunWidget(),
    builder: (context, offset, child) {
      return Transform.translate(offset: offset, child: child);
    },
  ),
  // TweenAnimationBuilder for the moon
  TweenAnimationBuilder<Offset>(
    duration: const Duration(seconds: 2),
    curve: Curves.bounceOut,
    tween:
        Tween<Offset>(begin: const Offset(500, 0), end: const Offset(0, 0)),
    child: const MoonWidget(),
    builder: (context, offset, child) {
      return Transform.translate(offset: offset, child: child);
    },
  )
]);

Here, you use two TweenAnimationBuilders to animate SunWidget and MoonWidget implicitly.

Your TweenAnimationBuilder has several arguments:

  • duration: Your animation duration.
  • curve: Here, you make SunWidget bounceIn and MoonWidget bounceOut.
  • tween: You used Tween to make the translation animation.
  • child: The non-moving part of your widget. In this case, the children are SunWidget and MoonWidget.
  • builder: Where you decide how to animate. Here, you used Transform.translate(). TweenAnimationBuilder calls it several times with an updated offset interpolated from your Tween.

Note that you display both the sun and the moon in a Stack because you haven’t handled the transition between the themes yet.

Hot reload to see the result.

Animation using TweenAnimationBuilder

Though you get an animation, it only animates on startup. It’s not easy to start it on a button press or launch the moon animation after the sun animation finishes. This shows that implicit animations are not the best tool here.