Get Fluttered: A Deceptively Simple Bottom AppBar Part #1

This widget is what we are learning to build today!

I started my journey of learning Flutter a couple of months ago. Coming from an Android background Flutter felt so much easier to work with. But in this phase of learning Flutter, I always found it difficult to find good resources for learning it. Hence, I decided to contribute my bit to the Flutter community by writing articles about different stuff, with a thought that you and I learn together. So this is my very first article!

What is Get Fluttered?
Get Fluttered is an article series in which I will be attempting to recreate some beautiful designs I find on the internet and share my learnings with you.

The gif that you see at the top is what we are going to build today. So first things first, this design inspiration comes from Vitaly Rubtsov’s Tab Bar Animation on Dribbble.

Any Prerequisites?
1) A fair bit of knowledge of how animations work in Flutter.
2) An attitude to not just give up when you don’t understand something. Google the heck out of the topic, watch videos and come back and try again.

Before writing any code let’s just take a look at our widget and see what all components would be required to build it. I see the following components -

Taking all the animations into consideration, there are a few more things (let’s name them KEYNOTES as I will be referring to them time and time again in the article) we need to keep in mind:-
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.

Okay, so that’s a lot that we need to achieve. But don’t worry, we’ll build it brick by brick, going one step at a time.

First, let’s just try to implement the KEYNOTE NO. 1
Our need is to be able to resize the desired option along its width as and when we require it. For this, we use the concept of a Flex. The Flex widgets (ex- Flexible, Spacer etc) allow us to size the element in proportion to other Flex widgets at the same hierarchy level. For example, if I consider two Flexible widgets, and provide them with a flex of 1 and 2 respectively, then they will take the available space in a 1:2 ratio along a particular axis. This axis is defined by their parent. If the parent is a Column, then the widgets flex in the vertical direction to fill the Column and if the parent is a Row, then the widgets flex in the horizontal direction to fill the Row.
So let’s see how can we use this to achieve our first KEYNOTE.

We define a List of flex values as flexValues and assign these flex values to the respective Flexible widgets. You might be thinking that if it was about proportionally sizing the widgets, then why are we giving them such large flex values such as 100 and 150. Rather, we should just provide them with 2 and 3 as these values give the same ratio. Let me explain, the flex can only be an integer. We are going to use these flex values to animate the click event. So if we animate our flex value to go from 2 to 3, the result would be an abrupt change, a change straight from 2 to 3. Instead, if we animate the flex value to go from 100 to 150, we are toning down that abruptness by 1/50th. 
We get the following output -

Now let’s include a small test animation. This animation shows us how the change in flex values affects the width of each of the individual options. We just make the following additions in the SimpleBottomAppBarState class -

We see the following output -

Can we now safely say that we now know how to implement the KEYNOTE NO. 1? Ummmm……well not really. So, what are we missing? A lot of widgets in Flutter take the dimensions of their parent if they do not have a child, but when they are provided with a child they take the dimensions of their child. So how does that affect us? As mentioned in the Documentation -

By default, the placeholder is sized to fit its container.

This basically means that the Placeholder widget is forcing the Flexible widget to expand to as much as possible without overflowing(along the direction specified by the Row). If we replace the Placeholder widget with a widget that doesn’t force the Flexible to expand, the Flexible will shrink to the size of its child.
To understand this, replace all the Placeholder widgets with the Icons(Home, Timeline, Events, History) that we need. Here is the code -

This is the output -

How to fix this? 
We somehow need to find a way to make the Icons force the Flexible to take as much space as possible. The solution to this is the fit property of the Flexible widget. The fit property can have two values, loose or tight. As the documentation reads -

loose → const FlexFit
The child can be at most as large as the available space (but is allowed to be smaller).
tight → const FlexFit
The child is forced to fill the available space.

Therefore, what we need is the ‘tight’ value for our fit property of the Flexible widget. So let’s make this change in the code -

This is the output -

We see that our Flexible widgets are taking as much space as possible without overflowing.
Now we can safely say that we have understood how to implement the KEYNOTE NO. 1.

Let us move on from this and try to implement the KEYNOTE NO. 2.
The KEYNOTE NO. 2 demands that the text must only be visible below the newly selected option. 
So how to do this? 
First, we need something to register the User’s clicks. I took the help of the FlatButton widget for this. As mentioned in the documentation -

Flat buttons have a minimum size of 88.0 by 36.0 which can be overidden with ButtonTheme.

So you might be thinking that this size might become an issue, but let’s go back to what we have learned while implementing the KEYNOTE NO. 1. We saw that the child resizes itself to the max allowed width and height if we make the fit property of the Flexible as ‘tight’. Hence, if this FlatButton is the child of our Flexible widget, it will resize itself to the maximum possible space available. Let’s see this in action by implementing the following code (I commented out the _controller.repeat() in the initState() method)-

This is the output -

Now let’s implement the children of these FlatButton widgets. The child of each of these FlatButton widget is a Column which has two children, an Icon and a Text . We make use of the mainAxisAlignment property of the Column widget to center the children vertically and also use the crossAxisAlignment property to the center the children horizontally. Here is my code -

Some of you might think that why are we setting the fontWeight of the Text to 400. The reason for this is that the FlatButton by default has its own Material , because of which the text would have appeared in bold.
This is the output -

Let’s finalize the KEYNOTE NO. 1 by animating the flex values of the Flexible widgets. At the same time, let’s use the Opacity widget over the Text widgets to implement the KEYNOTE NO. 2 as well.
This is how I implemented both of these things -

So there are a lot of changes that I made here. Firstly, I made two new variables currentIndex and previousIndex. They help me keep track of which options the User has selected. I introduced a new method called _onOptionClicked which updates the values of currentIndex and previousIndex when the user taps on any of the options. In the setState() method, I change the opacity values accordingly. I also changed the duration of the AnimationController from 3 seconds to 250 milliseconds.

This is the output -

Now we are left with the implementation of the remaining 2 KEYNOTES which I plan to cover in the next article. I know I know, its a terrible feeling when you want to go to the next article but it just isn’t posted by the author yet. Trust me I’ll try my very best to deliver it within a couple of days. Till then I would encourage you to go ahead and try to build it yourself.

Link to the Second Part -

And I would also like to thank all of you for reading it till the very end because I know there was a lot to go through, but in the end, if you learned something new then it’s all worth it.

P.S. -
1) Your interpretation of the components that are required to build this widget can be completely different than mine. I encourage you to take forward your way of thinking and build it through. Please do not forget to share it with me too! 
2) I would love to receive your feedback on as to how I can improve my future articles. Please don’t shy away from any sort of criticism too.
3) If you wish to follow me on Twitter, you are more than welcomed to do so.
Enjoy Fluttering!