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
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

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 AnimatedWidgets, especially in build():

  1. You use _animation‘s progress to evaluate each Tween-interpolated FadeAway object instead of directly using the _animation value in your widgets.
  2. You move and fade the temperature using temperatureCurrentValue.
  3. 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.

Final 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. :]

Challenge animations

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!

Sébastien Bel

Contributors

Sébastien Bel

Author

Vid Palčar

Tech Editor

Adriana Kutenko

Illustrator

Brian Moakley

Team Lead

Over 300 content creators. Join our team.