Sequenced Animations in Flutter
One of the many advantages of Flutter is the ability to create stunning and fluid animations. Animations are a key part of a modern mobile UX, be it in transitions, micro-interactions, state-changes and more.
The Flutter team does a great job helping developers get started with animation on their website.
For example the Staggered animation section provides a good starting point for creating more complex animations involving multiple elements.
In this post I will try to reproduce an animation made in Principle which I found on Principle Templates.
The benefit of using Principe is that you are able to easily break down the animation in timelines per element accompanied with information on size, position, opacity, … which we will need to reconstruct it in Flutter.
One thing to notice is that this animation has sequenced steps, meaning that one characteristic for the same element undergoes more than one transformation. For this we will need to use a TweenSequence, a type of tween that enables you to sequence multiple steps on a shared timeline, but more on that later.
Let’s start by setting up a Flutter project in Android Studio, or your preferred IDE.
If your IDE start with the typical increment demo remove all code until you end up with an empty scaffold.
Your main.dart should look like this:
Next we will add our assets to our project. You can easily export the needed assets from Principle for this purpose.
- Add the image assets to your project folder in an /images subfolder
- Don’t forget to define this folder in your pubspec.yaml
Now we are ready to start recreating the animation.
Setting up the stack
The animation works by placing all assets on top of each other and uses size and opacity to reveal and interact .
To do this in flutter we will start by adding a Stack widget. The Stack widget takes a list of children which it will paint on the screen in different layers which can overlap.
In the body of the Scaffold we will add the following:
This will add a first layer containing the Circle_Red.png image, we’ll give it a fixed size of 150 by 150 for now.
The Align widget is added to center align all Stack children.
When running the app you should see something like this:
Now we can add all assets in a similar way.
Make sure to use the following order when adding the assets to the stack:
When running the app you should see this:
As you notice, the Stack draws it’s layers from top to bottom.
Your build function should now look like this:
The animation controller
First we’ll start by creating the animation controller add a GestureDetector to trigger the start of the controller.
Add a TickerProviderStateMixin to our Widget State class. Simply put, this will generate ticks which will drive our animation.
The animation controller takes a TickerProviderStateMixin in its vsync parameter, in this case the class itself will be the ticker provided. We also give it a duration of 2 seconds.
Next wrap the Stack widget in a GestureDetector widget. In the onTap callback we will reset and start the animation
We’ll start simple with the grey heart with is visible at the start of the animation. It simply shrinks until it is no longer visible.
The elements starts at a size of 148 by 148 and shrinks to 0 by 0 using an easeInOut curve from 0.0 to 0.15 on the timeline. The achieve this, we’ll create a Tween that return a double, which we’ll use as width and height for the GreyHeart. Because we don’t want this transformation to last during the entire animation, we define an interval.
The apply this to the GreyHeart assets, we’ll wrap the widget in an AnimatedBuilder. The AnimatedBuilder will execute its build method on each tick of the AnimationController.
We’ll also use our animated value to provide the size of the widget.
Now run the app, every time you hit the image you should see the grey heart shrink and disappear.
Now we will do the same for the size of RedCircle, WhiteCircle and WaveEffect, but with different parameters.
We will also need to wrap the widgets in an AnimatedBuilder using the new tweens to drive the size of the assets as we did before.
What remains now is to animate the size of the RedHeart and Opacity of the WaveEffect. If you look at the timeline, you will see that these are more complicated as the others, because these involve different behaviour during the animation.
For example the RedHeart first , then it wil shrink again, to finally grow back a little.
To achieve this we will need to combine tweens in different intervals of the animation.
The TweenSequence class was created for this purpose, providing this functionality out of the box.
The TweenSequence works by providing a set of TweenSequenceItem
which consist of a Tween and a weight. The weight will determine how much time each tween will be allowed to run during the duration of the animation.
The order of items will determine which TweenSequenceItem is executed first, this is from top to bottom.
Let’s break this down.
The _redHeartSize animation object is created by calling
Into the TweenSequence we’ll provide the different steps called items.
Each of these items is TweenSequenceItem which holds a tween and a weight. Because we want each of the steps to have their specific curve, we chain a CurveTwee to each of the steps.
For weights we choose 20.0 for the first two, and the remainder for the last steps.
At last we’ll create an Animatable by calling .animate() on the TweenSequence. Because we want the sequence to start with a delay we setup a CurvedAnimation with an interval of 0.1 to 1.0
For the WaveEffect opacity this looks as follows.
Here you’ll see a similar structure, but we don’t define a curve for each Item. Because both steps use an easeInOut curve, we define this on the TweenSequence level and not on each TweenItem itself.
Again, don’t forget to wrap the widgets in an AnimatedBuilder and use the animation value to define the height and width.
For the opacity we’ll also wrap the widget in an Opacity widget and use the animation value to determine the opacity of the child widget.
The final result
Now we are ready running the full version and testing our animation.
Below you see an example running on an iOS simulator. Keep in mind this is in debug-mode, try running it on an actual device using ‘flutter run — release’ to experience it in full.
Check out the full code on Github
This post was written after giving a talk on animations at Flutter Meetup Belgium some time ago by our founder Thomas.