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
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
You can see this in action on this Dartpad.
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
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.
AnimatedContainer is an
ImplicitlyAnimatedWidget and it allows setting several properties of a child, including
margin and, the one relevant for us:
So, all I had to do was replace
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.
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.
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.)