Folding Options Menu in Flutter
Menus are one of the most important parts of any modern mobile Application. Primarily there are 2 types of Menus that you would have in an Application, Context Menu and Options Menu. I have already created a Focused Menu Package that would help you get an Exclusive Feel for your App’s Context Menu and now I would like to Introduce this Folding Menu package that you can use as an Options Menu in your Flutter Application.
Using the packing is fine but in this article, I’ll help you understand the code that goes into making this package. First, let’s take a look at how this looks in action…
Okay, so let’s get started
The first thing that we need to do is create a new Stateful Widget called FoldingMenu. Three basic properties that this FoldingMenu would require to work are:
- children: A set of child widgets to show as each folding tile.
- folded: To Control the folding state of Menu from the parent widget i.e. to control the folding of this menu from the AppBar action button of Screen.
- duration: To describe the time it should take for the menu to fold/unfold. It will look something like this:
The resulting widget will look something like this:
Now once this is done. The first thing that we need to take care of is, how the menu widget will respond to the change in the “folding” state. Well, for this we have a nice and simple override “didUpdateWidget” given to us by the Stateful Widget class. This gives us the previous instance of our widget i.e. FoldingMenu, which we can compare with our current instance and decided to perform a set of actions based on the result. The code will look something like this:
Now that we know, how we are going to detect the change in folding state and how our widget is going to respond. Let’s take a look at what we need to do when the folding state is changed….. You guessed it right! We need to animate the widget and for that, we need to set up an AnimationController and Animation instance in our widget. And since I want our animation to have a nice and smooth feel to it, I’ll also create an instance of CurvedAnimation in our state so that we can use it for all our animations. We’ll also add a listener to the AnimationController instance in the initState function, to update the UI with each new value (make sure to dispose it in the dispose function). Let me show you what our code should look like at this point and then I’ll explain how everything works:
Now, there are a few things that you might find surprising in the code above. You can see that we have two Animation instances here, namely _animation and _transAnimation. This is because, as the menu closes, each of its tiles will translate(move up or down) and rotate simultaneously and hence we have to animate them at the same time. We’ll use the same CurvedAnimation and AnimationController instance for these.
Along with this, there is also a function called initiateAnimations which takes an argument of “folded”. Based on this folded state we initialize/reset the _animation and _transAnimation. The value of transAnimation will go from 0 to 1 which we will multiply by the distance those tiles have to cover (depending upon the height of tile) and the value of _animation will range from 0 to 1.57 radians, which is equivalent to 90 degrees. Also, as soon as we set the value for animations in initiateAnimations, we have to reset the AnimationController and restart it by calling the forward() function.
Now, where do we call this initiateAnimations function?
Well, we use this in the initState function just after initializing the AnimationController and CurvedAnimation and then we call this function in the didUpdateWidget function so as to update the animations based on the new folded state.
Okay, so just for some visual context, I am going to be adding a bit of code to the build function to transform the child widgets based on the _animation value.
In the code above, we are using a Column to order child widgets from top to bottom and we are going to animate each of them individually using a Transform widget.
If you do not know how Transform and Perspective works, I suggest you to take a look at this amazing Article by Deven Joshi.
Now let’s use this Folding Menu and take a look at what our UI looks like:
We’re using Stack as a parent layout to display Folding Menu on top of our main Screen Content and currently, we are passing two params of children (with 4 ListTile widgets) and folded (with a value of “openMenu”) to control the folding and unfolding of the menu. The result is going to look something like this:
Notice, how tiles are rotating with an alignment of TopCenter. But do we really want them to rotate this way?
If you think about this, the behavior we want is relative to Window Blinds. The first tile (i.e odd tile) should rotate from its TopCenter, whereas the second tile (i.e even tile) should rotate from its BottomCenter.
Also, notice that tiles are fixed at a position and we want them to move down as they rotate when the menu opens and move up as they rotate back when the menu closes. For this, we have to calculate the height of the tiles. Keep in mind that each tile should be equal in height. To get this height we can use the addPostFrameCallback function of WidgetsBinding class. This function is called when the very first frame of the Widget is rendered. You can learn more about this here.
This is all fine but, even in addPostFrameCallback, how would we actually get the height of tile without any reference to each tile. Well, for this we have to take the children that we receive in the FoldingMenu and loop through them to create a new List called “widgets”. In this loop, we will take each child and wrap it with a Container widget while assigning it a new instance of GlobalKey as a key (we’ll also put this in a list called “keys”) and we will take the index of each child. We will add this as a list of values [index, Container] to the “widgets” list. This index will help us in detecting if the list tile is even or odd. We will then use this key in the addPostFrameCallback to get the height of the list tiles. Along with this, we will have to get the position of each tile so as to translate them exactly where they should be when we animate. We will put this in a Map instance called positions. This code will go into the initState function which will now look something like this:
Now, let’s update the build function to make use of these new collections (keys, widgets, positions, etc)
You can see that based on what we did earlier, we now loop through the list “widgets” that we create in initState rather than looping through raw “widgets.children” that we receive in FoldingMenu. We also use the index to detect the odd/even tiles and use the isOdd to select the appropriate alignment and rotation value. The result of this will look something like this:
Now, Let’s work on tile translation, for which we will use the _transAnimation instance and positions list. Earlier in this code, we are calculating the height of the tile and storing it in a variable called elementHeight. Now using this, we are going to translate tiles from the top of FoldingMenu to the position each tile is supposed to reach. With this our build function will look like this:
For Translation, we are wrapping our previous Rotation transform with another Transform. The alignment for this is going to always be TopCenter and we are going to use translationValues function of Matrix4 to provide the value for transform. We are only going to provide the “Y” translation value (line 13,14 of the snippet above) since we only need to move the tiles vertically. Now with this minor change, our app will run like this:
And with this, we now have a working folding menu. The only thing that remains is the shadow that you saw in the original package.
Since this article has already been too long, I leave it as a challenge to you 🎯. And if you have a problem implementing the shadows you can check out the original package code here:
A fancy easy to use Folding Menu for Flutter Applications - retroportalstudio/folding_menu
So, I hope you’ve learned a lot from this article. Also, check out the tutorial for using this package in your own applications. Links are as follows 👇🏻:
And, if you wish to support what I do, consider following me on:
Happy Coding! ✌😁