Flutter Animations Simplified II

Gonzalo Campos
6 min readFeb 15, 2021

In the previous chapter, we learned how to use Flutter’s implicit animations, which, in a few words, manage all the animation stuff for us: we have nothing to worry about but the values we are going to apply to our widget.

We used some of the most common widgets, such as AnimatedOpacity or, more heavily, AnimatedContainer.

But then, after using them, we may be wondering: what if there is no implicit animations that can fit my requirements? I think any programmer may think this about the built-in features of any technology.

That is why Flutter surprises us with other way of building implicit animations. We don’t have to worry about the animation hard part (yet), and they are way more flexible, allowing us to have more energetic UI with few code.

IMPORTANT: all the code present in this article is compatible with Flutter 2!

Enter Tween classes

Ok, first of all, do you recall what implicit animations did to your values? The answer: interpolation. For short: interpolation is the process of estimating a set of values given two known values. In the built-in implicit animations, this process was completely black-boxed from you. The widget managed everything and you only had to provide the initial value and the end value. But now, to create you own implicit animations, you have to do a little more of work.

However, to write some code that makes this interpolation can be a tedious task, specially if you make it several times (violating the DRY principle). That’s why Flutter provides the Tween classes. And yeah, the name Tween can make almost no sense (at first), but this is because it is a short version of between. You can see it like I do: the values beTWEEN this value and this one.

One of the first and more intuitive tween classes you can use is ColorTween, that, as you guessed it, will interpolate between two Color values:

ColorTween(begin: Colors.blueAccent, end: Colors.redAccent);

At first sight, this can seem a complex process. But, under the hood, this particular interpolation is being performed using only numbers. In a very strict way, colors in computers are treated as numbers. The tween class will interpolate every ARGB (alpha, red, green, blue) components (which are all integers) individually, and this will let us with 4 integer interpolations.

Using the Tween

So far, we’ve known that interpolation and Tween classes are important. We have the materials to create our animations but not the tools. That’s why we are using the TweenAnimationBuilder and it will receive our tween value as a parameter, letting us to use its values whenever we need. In addition to the tween value, it will receive a duration (again) and a builder function:

@override
Widget build(BuildContext context) {
return TweenAnimationBuilder(
tween: ColorTween(
begin: Colors.blueAccent,
end: Colors.redAccent,
),
duration: Duration(seconds: 3),
builder: (context, color, child) {
return Container(
child: Image.asset('assets/ditto.png'),
color: color,
);
},
);
}
Figure 1. Ditto’s animation using ColorTween

The builder function must return the widget that is going to use the tween’s values, and it will receive the BuildContext, the interpolated value, and a Widget (more on this later). The type of the interpolated value is dynamic, because in theory we can interpolate almost any kind of value we can think of (I’ll go deeper on this in a further section).

You must return the widget you’re going to add to you widget tree and, in my case, I’m adding the interpolated color to my root Container. What is going to happen is: while the TweenAnimationBuilder is using our Tween value, it will rebuild our widget every time the value changes by calling our builder function, giving the impression that an animation is occurring.

In this particular case, the given snippet can even be used in a StatelessWidget, but in a very limited way, because the animation will be launched only once, and with the same values. We can make things differently by using a StatefulWidget.

As in the previous chapter, we can define a new property:

class _ColorTweenDittoState extends State<ColorTweenDitto> {
Color _backgroundColor = Colors.blueAccent;

Use it in our tween as the end value:

@override
Widget build(BuildContext context) {
return TweenAnimationBuilder(
tween: ColorTween(
begin: Colors.blueAccent, end: _backgroundColor,
),

And change its value within a setState function:

void _changeColor() {
setState(() {
_backgroundColor = _backgroundColor == Colors.blueAccent
? Colors.redAccent
: Colors.blueAccent;
});
}
Figure 2. Ditto’s animation using a StatefulWidget.

As you can see, the value of backgroundColor will oscillate between two different colors. Every time this value changes, the widget will be reconstructed and the animation is relaunched.

Oh, and remember the child parameter? Well, it is only a widget that is part of the subtree of your complete widget (the one that is being returned in the builder) that doesn’t have any dependency on the interpolated values. This can be a good thing because, if it is not using the interpolated value, it can be drawn only once, optimizing performance in a dramatic way since it doesn’t need to be rebuilt. Maybe in little widgets it may not be noticeable, but imagine a widget with a lot of nesting levels. In our case, we can notice that the Image widget is not using the color value and we can put it in the child parameter:

@override
Widget
build(BuildContext context) {
return TweenAnimationBuilder(
tween: ColorTween(
begin: Colors.blueAccent,
end: _backgroundColor,
),
duration: Duration(seconds: 2),
child: Image.asset('assets/ditto.png'),
builder: (context, color, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(child: child, color: color),
TextButton(
onPressed: _changeColor,
child: Text('Animate'),
)
],
);
},
);
}

There are other tweens that work much the same as ColorTween, but with different kind of values, such as IntTween, SizeTween, RectTween and so on. You can take a look to the tween.dart file in the Flutter’s official repo.

Creating your own Tweens

The tween classes have a super class called Tween<T>. It will allow you to create any kind of tween that you like. The most common is Tween<double>, that will interpolate only with values of the type double.

But what if we want, for example, create a tween of a custom type? There are some restrictions to this. First, you data type must override the + and - operators, and the * operator receiving a double. We can take the simplest data type, a Point and implement this operators:

class Point {
final double x;
final double y;
Point(this.x, this.y); Point operator +(Point point) =>
Point(this.x + point.x, this.y + point.y);
Point operator -(Point point) =>
Point(this.x - point.x, this.y - point.y);
Point operator *(double scalar) =>
Point(this.x * scalar, this.y * scalar);
}

Now let's apply it in a Stack widget:

@override
Widget build(BuildContext context) {
return TweenAnimationBuilder(
tween: Tween<Point>( // <-- custom tween
begin: Point(0, 0),
end: Point(200, 200),
),
duration: Duration(seconds: 3),
child: Image.asset('assets/ditto.png'),
builder: (_, Point point, child) {
return Stack(
alignment: Alignment.center,
children: [
Positioned(
child: child!,
bottom: point.y,
left: point.x,
)
],
);
},
);
}
Figure 3. Ditto’s movement with custom tween.

The Repo

One again, you can take a look to all the code in my Github repo.

Conclusion

In this chapter, we saw that tweens and interpolation are very important while creating animations. They are one of the key concepts of animations in Flutter, since they allow us to define them by having only an initial value and an end value. Anything else is handled by the tweens and we just need to worry about to set good value for out animations to look good to the eyes.

--

--