Get Fluttered: A Deceptively Simple Bottom App Bar Part #2
I wholeheartedly welcome you to the continuation of building our widget!
I do believe that you have gone through the First Part and you are all excited to finish the Second one.
A Recap of the KEYNOTES that we have to achieve to build this widget -
1) The newly selected option over the complete duration of the animation, ends up taking more space than all of the other options.
2) The Text is only visible in the currently selected option.
3) When a new option is selected, the text slides and fades from the currently selected option and then appears again, sliding its way into the newly selected option.
4) The newly selected option’s icon animates in 3 different ways — it slides up, it slides towards the left or the right, and it skews. This animation runs in complete reverse for the previously selected option.
In the First Part, we achieved the KEYNOTES 1 and 2. Today we will be implementing the remaining two KEYNOTES.
This is where left off the last time -
Looking at KEYNOTE NO. 3, the very first thing that we need to focus upon is how to make the Text slide. Let’s see what the documentation has to say about the
Applies a translation transformation before painting its child.
The translation is expressed as a Offset scaled to the child’s size. For example, an Offset with a
dxof 0.25 will result in a horizontal translation of one quarter the width of the child.
FractionalTranslation widget be able to fulfill our needs?
Well, it is giving us a way to slide the Text, but we run into another issue here. As the translation is based solely on the child’s size, if the children have different sizes, then the translations for the same ‘dx’ will result in non-uniform translation behavior. I’ll demonstrate this by the following sample code-
This is the output-
As you can see that the Texts have shifted non-uniformly(bigger Texts have translated more, represented by the blue lines). This non-uniform behavior would result in an inconsistent animation (bigger Texts will slide faster for the same Offset value).
How do we fix this?
We know that the
FractionalTranslation widget translates its child proportional to the size of the child. If we desire to translate the Texts (in the horizontal direction) with the same proportion, then we need to make sure that all the
FractionalTranslation widgets have the same sized children. But if the Strings inside the Texts are different, then obviously the size of the Texts will also be different. Which means that we need to introduce a new widget in between the
Text widget and the
FractionalTranslation widget. The job of this widget should be to provide us with a box which not only encloses the
Text widget but also sets an equal size for all the
FractionalTranslation widgets. For now, assume that this imaginary widget is ‘X’.
If the width of this X widget is equal to the width of the entire ‘option’, then a value of 1.0 for ‘dx’ in the
Offset , would result in a translation equal to the width of the entire option. Which basically means that the Text has gone out of bounds of its ‘option’, which is what we want. Confused? Let me try to explain it with a diagram -
So now I hope you would agree that, by this approach, irrespective of how big or small our ‘option’ is, the Text would always go out of bounds at a ‘dx’ of 1.0 or -1.0. Hence, we are reaching a number here, which will help us in our
Okay, so what is this mysterious widget ‘X’?
Center widget. Lol, I find it kinda funny that out of all the widgets, a damn
Center widget does our job. But How?
As usual, let’s see what the documentation has to say-
This widget will be as big as possible if its dimensions are constrained and widthFactor and heightFactor are null.
Simply put, this line means that if the
widthFactor and the
heightFactor are not provided, the
Center widget will try to be as big as possible unless forced otherwise. This
Center widget is a child of a
Column lays out children vertically. The
Column lays out
children with the minimum height needed for them. Hence, the
Center widget doesn’t push its bounds vertically. But the width of the
Column is derived by its maximum width child. Hence, the
Center is forcing the
Column to push its bounds and be as big as possible. So, the
Column becomes as big as its parent which is a
FlatButton, which in itself is as big as the entire ‘option’. Therefore, this results in the
Center widget taking the whole width of the ‘option’.
Let me show you what I mean by a simple change in above sample code. All you have to do is to wrap all the
Text widgets with a
This is the output-
As you can see, that all of the non selected ‘option’ Texts have shifted equally and the selected ‘option’ Text has shifted a little more. This is because every
Text translated in the same proportion of its option’s width.
So we have achieved a uniform behavior and also got 1.0 or -1.0 as values where we can be sure that the Text will definitely go out of bounds of their ‘option’. But the Text is still visible, even though it’s out of bounds. To resolve this we will use
ClipRect . Quite understood that we will again take a look at the documentation -
A widget that clips its child using a rectangle.
By default, ClipRect prevents its child from painting outside its bounds, but the size and location of the clip rect can be customized using a custom clipper.
Hence, just wrapping the
FractionalTranslation widget with a
ClipRect does the trick for us.
Before showing you the final code for this, one more important thing is left. Animating all of it!
Text always slides from the previously selected option to the currently selected option. Which means that if the
currentIndex is greater than the
previousIndex then the Text would slide towards the right, whereas if the
currentIndex is lesser than the
previousIndex then the Text would slide towards the left. Looking carefully at how the animations are played out, we can see that they are staggered animations. In the first half of the animation, the Text slides and disappears from the previously selected ‘option’, then in the second half it reappears while continuing sliding in the currently selected ‘option’. This is how I implemented all of this -
end values for the different
Tweens are purely selected on the basis of trial and error. You can choose whichever values you feel like are providing a result which is more closer to the original design.
This is the output -
Okaaay, so that took to take in. Now we are left with last KEYNOTE. The good news is that it is relatively easier to understand and implement. Let’s finish the job.
I mentioned that the KEYNOTE 4 involves three different animations. We’ll cover them one by one.
‘slides up’ -
If you look carefully, in the original design, the Icons inside the non-selected options are at a lower height than compared to the Icon of the selected option. When the user selects a new option, the previously selected option’s
Icon slides down, whereas the newly selected option’s
Icon slides up. A simple translation is all that is required to achieve this. I created a
List for storing these vertical shift values and provide them to the
Transform.translate widget. The farthest translation value in the upward direction is
4.0 dp and in the downward direction is
8.0 dp. Here is the code -
This is the output -
‘slides left or right’ -
Well, we have already implemented this. In case you didn’t notice, we reached this milestone way back when we implemented the Flexible widget in the First Part.
Icons skew in the vertical direction. The newly selected
Icon skews upwards in the first half of the animation and then comes back to the normal form during the second half of the animation. For this, we will use the
Transform widget again, but this time a little differently.
I tweaked the
FlatButton a bit so that we don’t see its default click animation behavior. Here is the final code for our widget -
Here is our final result -
1) Writing an article was a very different experience. It was tough but something new. I would like to believe that I did a decent job 😂. I do hope you guys enjoyed it and learned something.
2) What should I build next?? Please tell me as I am little confused on this, lol.
3) Please do tell about how you felt about this article. Do give me feedback on what you liked the most, what you didn’t like at all, what you think I should include in my next article. Your feedback would greatly help me improve.
4) My Twitter