Notes: 04. Create Custom Implicit Animations
The student materials have been reviewed and are updated as of August 2022.
The updated material uses null safety and also deploys the use of const keyword with constant constructors, use of single quotes over double quotes as per the Flutter lint rules that is used to encourage good coding practises. Also, the return value of the Tween variable is explicitly mentioned to avoids errors of passing the incompatatible Tween to TweenAnimtionBuilder.
In earlier episodes, we introduced ImplicitlyAnimatedWidgets.
We saw that Flutter provides lots of these ready made widgets and all we have to do is to find the one that suits our animation needs.
But what if the widgets provided doesnt suit our specific needs?
What if we want to animate a specific properties of a widget and can’t find any AnimatedFoo widget that fulfils this need?
What do we do? Do we abandon Flutter and go native? Or do we just settle and switch to Phonegap?
Well, Flutter provides us with the TweenAnimationBuilder widget that helps us create custom implicit animations.
It takes in 3 required properties:
- the duration
- builder
- and the Tween
You should be used to the duration property by now.
The builder method is used to draw the widget we would be animating and it is called whenever the animation value changes.
So it simply rebuilds the widget to create the desired animation effect.
Finally, we have the tween property, and this specifies the values we want to animate between.
Tween is a short form for inbetween and it just helps us interpolate between a beginning and an ending value.
Okay, let’s see how to use the this widget.
Back in VSCode, we have a Determinate CircularProgressIndicator and a Text widget that shows us the progress of the indicator in percentage.
Clicking the FAB increments the progress but these two widgets doesnt animate.
They just jump to the new position and value as soon as the state changes.
Currently, flutter doesnt provide us an AnimatedDeterminateCircularProgressIndicator and AnimatedText widget.
So, in this case, we could use the TweenAnimationBuilder to animate these properties.
Go ahead and update your code to the following:
...
TweenAnimationBuilder(
duration: const Duration(milliseconds: 500),
tween: Tween<double>(begin: 0.0, end: progress),
builder: (_, double value, __) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
value: value,
),
Text("${(value * 100).round()}"),
],
);
},
),
...
In here, we passed the TweenAnimationBuilder as the child of the Center widget.
We gave it a duration of 500 milliseconds.
We then returned the Column as the child of the builder.
For the tween, we set the begin value to 0.0 and end value to 1.0
The Tween class is an Animatable and it is responsible for interpolating the values between the begin and the end target value.
The builder callback takes in 3 parameters: the context, the value and the child. I added underscores for the first and last parameters since we wont be using them now.
The second parameter is the most important one which we would be using.
It stores the animation value that the TweenAnimationBuilder generates and this change in value is what triggers the widget rebuild, thus producing the animation effect.
We use this value inside our widget as the value for the CircularProgressIndicator.
The value of a CircularProgressIndicator goes from 0.0 to 1.0 for a full cycle.
Also, the value is used to calculate the percentage value of the Text.
Save your work and try it out.
You can see, the CircularProgressIndicator and the Text widget value animates accordingly.
Now, let’s talk a bit more about the TweenAnimationBuilder.
The value from the builder is dependent on the type of Tween passed.
The default TweenAnimationBuilder is of type double.
This means that the Tween must be of type double which in turn makes the value produced from the builder a double type.
You can have custom begin and end values. The common one is from 0 to 1 but you can have a begin value of 100 and end value of 300.0 or whatever suits you needs.
This is useful when you dont want to do some arithmetic like we did to get yor desired value. So for example, if we just wanted to animate the percent value in the text, then a tween that starts at 0.0 and ends at 100.0 would serve the same purpose.
But you’re not limited to just type double.
You can create a Color or even an Offset animation. (Fade in a basic example of different types: AlignTween, SizeTween, ColorTween, OffsetTween, IntTween)
And your TweenAnimationBuilder‘s builder callback’s value must be updated to match this.
Now let’s talk about some performance optimizations when using this widget.
In here, i made the FAB animate in with a rotate animation.
We’re using the TweenAnimationBuilder because Flutter doesn’t provide an AnimatedRotation widget. So this is a good scenario where we could also use the TweenAnimationBuilder.
I’m using pi from dart’s math library to give it a rotation of 180 degree because. pi’s value in degree is 180 degrees. I multiplied it with the animation value so it animated from 0 to 180 degrees. Let’s do a hot restart to see this in action.
First, you can notice the animation plays immediately the widdget is loaded.
This is another use of TweenAnimationBuilder.
They can be used to play animations immediately a widget loads and this is possible because of the Tween we provided.
A tween is an animatable and once the target value is provided from the start, it animates to the target value immediately.
Previously we used setState to affect the target value.
Now, let’s take a look at the builder.
Currently, the only widget that directly makes use of the animation value is the Transform widget. The FAB doesnt directly use this.
But the builder doesnt care. Whenever the animation value changes, it renders the entire widget tree in the builder.
The TweenAnimationBuilder gives us the child property which let’s us serve the widget subtree that doesn’t depend on the animation value directly.
Let’s update our code to use this:
...
static final Tween<double> fabTween = Tween(begin: 0.0, end: 1.0);
...
floatingActionButton: TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 500),
curve: Curves.bounceOut,
tween: fabTween,
child: FloatingActionButton(
onPressed: progress > 0.9
? null
: () {
setState(() {
progress += 0.1;
});
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
builder: (_, value, child) {
// return Transform.rotate(
return Transform.scale(
scale: value,
// angle: pi*value,
child: child,
);
},
),
...
Save your work and do a hot restart. First, you’ll notice i changed the animation to scale animation. The rotate animation looked kinda boring for me.
Okay let’s understand the changes we made.
In here, we passed the FAB to the child property.
Any widget passed to this property would not be rebuilt whenever the animation value changes.
Then inside the builder callback function, i removed the double underscore for the third parameter and replaced it with a named variable called child since we would be needed it now.
We used this child param by passing it as the child of the Transform widget.
So the child inside the builder is referring to the widget passed to the child property of the TweenAnimationBuilder.
With this setup, Flutter knows that it should not rebuild the FAB since it doesnt change during the animation.
You might not notice the benefit of this now but imagine if the FAB was a more complex widget. This simple optimization would be saving you a lot and make your app more performant. The second subtle optimization we did was to store the tween object in a static final variable. Why did we do this?
Well, we did this because in this case, the tween’s begin and end values never change. They’re fixed unlike the one for the progress indicator above in which setState affects its end value. By declaring the tween variable to be static and final, the exact same instance is always used whenever the tween is needed.