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 FractionalTranslation widget-

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 dx of 0.25 will result in a horizontal translation of one quarter the width of the child.

Will the 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 -

The yellowish green rectangle represents the widget X

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 Tween values.

Okay, so what is this mysterious widget ‘X’?

It’s the 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 . The 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 Center widget.

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!

The 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 -

The begin and 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.

‘skews’ -

The 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 -