Marcin Szałek
Flutter Community
Published in
7 min readNov 14, 2018

--

Hello there! I’m back with a new post about implementing Johny Vino‘s BMI Calculator design. If you are not familiar with previous posts, I recommend checking them out. This time we will focus on an animation between the input screen and the result screen. We will change the shape, position and size of a Widget using only one AnimationController. Let’s see how it goes.

The goal

Since we started working on this project, the design changed a lot, therefore the transition animation also changed. However, for the educational purpose, I think it will be much better (and more fun!) if we adapt old animation to the new design. Since the animation is fairly complicated and we already have some advanced stuff here (especially in PacmanSlider), I will be skipping some of the code changes that are only relevant for this project and I will try to focus on general approaches I take to create that animation. If you are interested in the whole code, check out Github repository.

Coming back to work, this is the behavior we would like to achieve:

And this is what we have so far:

Setting up AnimationController

In order to work with animations, we need an AnimationController, here we will use only one named _submitAnimationController. This controller will have 2-second duration and it will get started when the user submits the PacMan slider. So far it makes on effect on the app.

Animating slider’s border

We will start our transition animation with changing slider’s borders to be more rounded. To do that, we will use BorderRadiusTween which will help us animating from one BorderRadius to another. First, let me show the code and then explain it.

The first thing we need to do is pass AnimationController to the PacmanSlider widget, it can be easily done by just adding a new argument to the constructor. Then, we need to create an Animation. As I said earlier, we can use BorderRadiusTween for that. All we need to do is specify starting and ending radius and Flutter will take care of the rest. We also do one more thing with the Animation, we specify Interval curve, which means that our border animation will not be run during whole 2 seconds of AnimationController but only from start to 7% of those 2 seconds. Then we will wrap our widget in AnimatedBuilder which will take care of rebuilding the slider everytime AnimationController changes its value. After that we just need to use the value from_borderAnimation and see the result:

Shrinking the slider down

Great, now let’s try to shrink that slider so that it has the shape of a circle. Let’s start by creating new Animation that will be responsible for animating widget’s width. The difference between this animation and the border one is that we cannot initialize it in initState method, because we want to animate slider’s width obtained from LayoutBuilder during build method. The animation we create will become our source of widget’s width instead of width field.

Theoretically, it already could work, however, inside the GestureDetector there is a lot of stuff that doesn’t go well with resizing that widget, so we will do a little hack and replace current content of slider with an empty container if the animation is animating. Thanks to that we won’t have to bother on how Pacman icon or the dots behave when sliders get too narrow. We will use animation’s isDismissed property which tells us if an animation is stopped at the beginning.

Now we can see our slider shrinking down to the shape of a circle:

Moving the dot

We got the slider animation done, now we can work on the input page. We would like to move the dot we got from the slider to the middle of the screen. To achieve that we will create a Widget that will cover the whole Scaffold after the slider finishes its animation. We will start by wrapping the Scaffold in Stack.

Notice that we placed TransitionDot after the Scaffold. This way it can cover the default Scaffold. Now let’s take a look at the TransitionDot.

Let’s break it down step by step:

  • We are extending AnimatedWidget. It means that instead of wrapping whole Widget in AnimatedBuilder, it will be rebuilt every time the animation from constructor changes its value.
  • We create a new dot widget. It will take place of old slider, the idea is to make it appear in the same position the slider ended up after shrinking down.
  • We wrap the whole widget inside IgnorePointer, so that it can cover the whole screen and not interact with all the controls.
  • We specify the widget’s opacity. If animation is below 0.15 (point where slider animation ends), opacity is set to 0, so we cannot see the widget, otherwise we make it visible.
  • Our actual widget is a Scaffold, same as the one from InputPage but without AppBar and with different body.
  • So far let’s just have a Column with a dot as a body.

The effect looks like this:

Well, not quite impressive, right? Now we can actually animate the position of the dot. Not sure if my idea of how to do it is good, but it works so we will stick with it. So, what we want to do is surround the dot with Spacers inside the Column. Spacer is an empty widget that takes as much space as possible, it also has a flex attribute that can specify the relation of how much space of the whole container multiple spacers should take. The goal is to start with a spacer with high flex on top and very low flex in the bottom. Then, if we change the value of flexes to be equal, the dot will be between two equal spacers so it will be in the middle. To change the flex values we will use IntTween which will increase int values from 0 to 50.

Those changes cause such result:

Animating dot’s size

Once we get the dot in the middle, we can focus on animating its size. We will create another animation that will be responsible for that, but first, we need to deal with the main problem which is that our dot is supposed to be expanded and shrunk multiple times. Usually, in scenarios like this, we add a listener to the animation and repeat it once it’s done. However, since we are having an Interval animaiton I can’t see how it could work. So how are we going to deal with that? We are going to create our own Animatable.

As you can see, we used sin function to handle changing the size back and forward. Our animation will go from defaultSize to defaultSize + expansionRange to defaultSize - expansionRange and then come back to deafultSize 2 times. Let’s use it 🙂

So we use the LoopedSizeAnimation in a combination with Interval. That way our dot will change its size after it got moved to the center. It should look like this:

Expanding the dot

Awesome, now we can modify that animation so that in the end it will cover the whole screen. To do that, we are going to mark a point in time, after which the dot stops pulsing and starts expanding.

What happens here is that if the time of the animation is below 80%, it will be pulsing. After that, it starts to rapidly grow from default 52 logical pixels to 2000 which should cover the whole screen. Now we need to add small changes in using the animation:

We changed two things:

  • We make sure that the height and width are smaller than the screen.
  • We make sure that dot changes shape to rectangle when it gets close to the edge. Otherwise, it wouldn’t be able to cover the whole screen.

Let’s look at the result:

Fade transition

The only thing left is navigating to the new page. We will create a very simple Route so that the transition from the blue screen to the next one will be smooth.

Now let’s use this FadeRoute to navigate to ResultPage. I won’t describe how this page is created since there is nothing interested there. If you are interested, you can check it out here. To navigate to the new page, we will simply add a listener to _submitAnimationController and call Navigator once the controller is completed.

Alright, let’s see the final result!

And that’s it! I hope you liked this post, if there is anything unclear or if you think I could explain or implement something better, please leave a comment. 🙂

I expect it to be the last post of the BMI Calculator since the design part is mostly done. I really enjoyed doing this app and sharing the process with you. I hoped you enjoyed it too.

If you liked the whole series, leave a star on the Github repository where you can find all the code, I would appreciate it. 🙂

Cheers 🙂

Originally published at marcinszalek.pl on November 14, 2018.

--

--