Flutter: Custom Expandable i.e. ExpansionPanel

Tsung-Wei Hsu
3 min readJan 22, 2023

--

A view containing a clickable header and a expandable body is almost a standard design pattern in every app. For that, Flutter has already a built-in widget called ExpansionPanelList combining with ExpansionPanel(s) that create expandable content. However, it is not highly customisable with e.g. a chevron icon that cannot be removed.

Surely, there are a handful of plugins such as expandable that serves the exact purpose. But what if we want the maximum* control optically as well as functionally? The best answer is of course Do-It-Ourselves!

1️⃣ Using AnimatedContainer()
It might be the fastest and simplest way to achieve such behaviour when all bodies have the fixed* height. Starting with a StatefulWidget that contains a variable _currentHeight and a final _expandedHeight of your choice:

Now, after the state change has been taken care of, we create the trigger of the expandable, namely the header, and its body inside the build method:

That’s pretty much it with a result in action:

Expandables with a fixed height

Through manipulating the height of the container by switching the state of the current height between zero and the expanded height, the effect of an expandable view can be simulated. But what if the height depends on the size of the content?

2️⃣ Using SizeTransition() with AnimationController
This approach can handle the bodies of dynamic* height. Since it is a deeper level of AnimatedContainer(), it might require the basic understanding of how animations are built in Flutter. Let’s first create a snippet of a StatefulWidget with an AnimationController, an Animation, a Tween and a state tracker. We initialise their values in the initState():

Use Tween() to define the start and end value of the animation. In our case, we want to completely hide and expose the expandable body. Thus, the start value of the body size should be 0 (collapse state) and the end 1 (expanded state). With forward() of the controller, the value moves from 0 to 1 within the duration of 150 milliseconds simulating the expansion effect. Similarly, reverse() does the opposite from 1 to 0.

Finally, a slight adjustment to the build method by adding SizeTransition() and the animation we’ve just configured with a given body widget:

Expandables with different heights

Tada! It is that magical and simple to maximise the customisability of your expandable, where its functionality can certainly be further enriched as you need it to be. For instance, adding an additional chevron at the location you desire by using a second animation with RotationTransition(), or creating a controller that allows only one expandable to be opened at the same time among multiple others.

Before you go, these might also interest you:

--

--

Tsung-Wei Hsu

frontend | web and mobile | apps | programming and making coffee from Frankfurt, Germany ☕️