I’m a Flutter newbie (my day job is Android developer), and it took me some time to understand how animations work in Flutter. In particular, most posts and videos on this topic show how to achieve one of these:
- How to animate properties of a Stateless widget between a fixed pair of start and end values
- How to animate properties of a Stateful widget from whatever the current value is, to an end value.
What I wanted to achieve was a mix of the two. My objective was:
Animate a Stateless widget to an end value, beginning with whatever the current value is
In this post, we’re going to look at exactly that.
TL;DR: Within your Stateless widget, use an implicit animation (a widget that extend from ImplicitlyAnimatedWidget
) to animate the properties you wish to.
This is the animation that we are going to build.
As you can see, it is a very simple animation. Every time you tap the container, it animates:
- The background color (this animates between a shade of blue and completely transparent)
- A drop shadow for the background
These properties are applied wrapping our content in a DecoratedBox
widget, and applying these properties as a BoxDecoration(color:, borderRadius:, boxShadow:[])
Starting point: One-time animation
To start off, we’ll see how to achieve this as a one-time animation, without the tap handling. This requires us to create a StatefulWidget
, provide an AnimationController
and provide a tween. All this is pretty standard for making an animation in Flutter.
Here’s the tween definition
And here’s how we use it inside of a DecoratedBoxTransition
You can see the full runnable example on this DartPad snippet.
Toggling between states on tap
As the next step, we’ll introduce an isSelected
state that we toggle when the user taps on our widget. This allows us to make the animation go forward/reverse depending on the state.
First, we introduce a bool field in our state class
Then, in our build
method we wrap our DecoratedBoxTransition
in a GestureDetector
to handle taps
Finally, instead of starting the animation in initState()
, we start the animation in either forward or reverse direction in _handleTap
, depending on the _isSelected
state. And of course, we need to update the state by calling setState()
You can see this in action on this Dartpad.
Going Stateless
The previous solutions both work, but what I really wanted was to make my widget stateless. The reason is that in my app, state is managed outside the widgets. When a user taps on a widget, it simply calls a Function
that was passed to it. I have a different component in my architecture that updates the state and passes back the new isSelected
to me.
So, can we just cheat and give the DecoratedBoxTransition
only the end value so that it begins with whatever the current value is?
Unfortunately, this does not work. DecoratedBoxTransition
expects both begin
and end
to be not null. This is because DecoratedBoxTransition
is not an ImplicitlyAnimatedWidget
. Understanding this was a light bulb moment for me.
To achieve what I want, what we really need is an implicit animation. Is there one that suits my needs? Indeed, there is.
Enter, AnimatedContainer
.
AnimatedContainer
is an ImplicitlyAnimatedWidget
and it allows setting several properties of a child, including constraints
, paddding
, margin
and, the one relevant for us: decoration
.
So, all I had to do was replace DecoratedBoxTransition
with AnimatedContainer
and voila! My widget works exactly as I expect it to.
Some relevant pieces of code: First, make the widget completely stateless by passing in the isSelected
and a function to handle taps as constructor parameters:
Then, in the build method, return an AnimatedContainer
, passing in a decoration
object that conditionally returns either an empty decoration or the one we want.
You can see the full working sample on this Dartpad.
Gotchas
I was able to get away with using a Stateless widget in my case because there already existed an ImplicitlyAnimatedWidget
that fulfils my needs. This might not always be the case, so you might end up needing to make your widget stateful anyway. If you are faced with this situation, you might also consider creating your own subclass of ImplicitlyAnimatedWidget
to perform the animation - so that you can keep your actual widget stateless.
Conclusion
In this post, we saw how to use implicit animations in Flutter to keep your widget stateless, and what the limitations are.
To see a real world use of the techniques described in this post, see the Covid19-India flutter app (where you switch between the categories like “active”, “recovered” etc.)