Getting Your Hands Dirty with Flutter: Basic Animations

Max Ermakov
ProAndroidDev
Published in
5 min readMar 26, 2018

--

Last time I promised to show how to use Flutter’s UI widgets and the Firebase Firestore plugin. However, we’re not doing that today. Instead, we’re talking about basic animations in Flutter, because, apparently, that’s all anyone ever cares about.

The quick answer is: yes, Flutter is damn good at animations. If that’s all you wanted to know, please feel free to go explore on your own. If not, let’s look at how I made it work.

Our focus today is the Tween (or in-between) animations for single widgets. Stuff like sharing element transitions between screens is a matter for another post.

I gave myself a task to create a small proof of concept (POC) with a sequence of animations. The whole thing took about four hours. Given that I’ve never done animations in Flutter ever before, the conclusion is: implementing basic animations in Flutter isn’t hard. Here’re the results:

In front of you is a sequence of animations with individual animation instances and animation controllers. Let’s take a closer look:

  1. The Swipe It prompt changes opacity from 1 to 0 and back.
  2. The quadratic bezier curve control point changes its position according to the black container width.
  3. The Tap Here text comes left to right from behind the screen and back.
  4. The Easy container comes outside the screen and moves from the bottom up and back.
  5. The Easy container performs a flipping horizontal animation.

Here’s how the code looks like. Now I’m going to dissect the animation even more, going into its creation step by step.

Let’s start by animating the opacity of Swipe It

Animations in Flutter are based on the typed Animation objects. We’re using the Animation<double> instance more often than the other templates because most of the widget properties are of the double type.

We’re also creating an AnimationController instance for animation management. When defining the AnimationController, we need to pass in a vsync object. Vsync prevents offscreen animations from consuming unnecessary resources.

Stateful widgets — like the Home screen — can be used as vsync objects. Just add a TickerProviderStateMixin to their State. If you only have one Ticker (e.g. one AnimationController) in the lifetime of your State, it would be more efficient to use a SingleTickerProviderStateMixin. This is relevant for all AnimationController instances in the snippet.

Now, let’s implement the Animation protocol. As we’re working on the opacity, we can apply the CurvedAnimation class directly. It changes values in the range from 0.0 to 1.0, which works quite well here.

Then we’re adding the AnimationStatusListener typedef to monitor the AnimationStatus enum. When AnimationController forward() is invoked, the animation emits a “completed” status after it’s finished. The reverse() command emits “dismissed”. Thus, the implementation of a current status listener provides a flashing effect.

What’s left to do is invoke a controller forward() method to launch this flashing effect whenever a screen is created.

Let’s look at the widget we’re animating with the animation and controller instances. Flutter is very kind to provide us with a FadeTransition widget. It’s opacity property can take an object and then wrap any widget child, like the row-icon-text combination in my case. Isn’t it just great?

Working on the steps 2 and 3

The next thing on our list is to change the position of the quadratic bezier curve control point. We’re combining it with the Tap Here text animation. Here’s how it looks like:

Comparing to the previous snippet, the only difference here is that we’re wrapping the CurvedAnimation with a Tween object. It represents the interpolation between custom beginning and end values.

Our bezier curve’s moving control point needs to be animated from the 0.3 container width to its full width on the x axis.

The text transition needs to be moved from behind of the screen (-100 px) to 0 on the x axis (if you’re using a padding object or a positive value of the preferred padding; in my case, it’s 16px).

The animated widget we’re using is a black container. It’s wrapped in a GestureDetector to catch horizontal swipes. To implement the bezier curve, I also added a CustomClipper to the container clipper property. You can find a simple CustomClipper implemented as the HillClipper class at the bottom of the snippet above.

Unfortunately, we cannot change the properties of CustomClipper directly. However, as we’re building our curve’s control points with the widget size, we can animate the size. Therefore, I applied the side menu animation to the Container width. I also used the onHorizontalDragEnd method to trigger the controller’s forward/reverse method according to the swipe velocity and direction.

To move Tap Here out of the screen, I used the Transform widget. It applies a transformation before painting its child, taking the Matrix4 value as a transform property. Matrix4 is an extended matrix implementation with many predefined actions you’re already used to: skew, rotate and, of course, translate.

Working on the Easy container

Here’s how I applied the translationValues method of Matrix4:

Although the snippet may appear quite large, it isn’t that complicated. I should point out that we’re not using Animation<double> for the Easy container to come up from the bottom of the screen. Instead, we’re going with the Animation<Offset> object, as the SlideTransition widget, designed to animate widgets relatively to their normal position, uses it as a position property.

If you look at the code snippet, the widget slides from Offset(0.0,3.0), which is a shift equal to its height taken three times, to Offset(0.0, 0.0), its original position, a.k.a. the middle of the screen (because the parent Alignment is center). For portrait orientation, it has to be outside of the screen right near its bottom. Makes sense, right?

Now we can flip it.

As you already know, the SlideTransition container holds Stack with two Transform widgets. Their transform properties accept Matrix4 identities and change their scale values with the _frontScale and _backScale Animation instances. This is a perfect scenario for using the same AnimationController, as the animation needs to run at the same time with the same duration.

It’s also a cool example for using the Inteval instance for the curve’s Animation property, because we need the _frontScale animation to start from the middle and reduce its height to zero, while the _backScale animation increases its height from half_size to the full size.

Aaand that’s about it

Next time, we’re actually going to discuss how to use Flutter’s UI widgets and the Firebase Firestore plug-in. As a bonus, we’ll also take a look at Hero Animations in Flutter.

Thank you for reading, and I hope you enjoyed it. If there are any questions, comments or suggestions, please don’t hesitate to leave them below.

--

--