Filter Menu – UI Challenge
In this post, I will do my first UI Challenge. As my goal I’ve picked this design by Anton Aheichanka from dribbble:
Let’s get to it!
First, we need to decompose this view into few smaller units:
- The clipped image on top
- Profile view
- List header
- List of items
- Animating items filtering
- Animated Floating Action Button
. . .
0. Starting point
As a starting point, we will create a simple app with a
Stack is a container Widget, that places widgets on top of each other. All of our widgets will be placed in it.
. . .
1. The clipped image
Let’s start with an image and add it to the
EDIT: Actually, I have changed it to
BoxFit.cover. Also I wrapped it in
bottom: nulllater on.
Now let’s add diagonal clipping. To do that, we need to create a
Our new class extends
CustomClipper<Path> and overrides two methods.
shouldReclip method tells framework when Clipper should be rerendered. We will set this to always return true for simplicity. In the production app, we might want to add actual logic here.
getClip method returns a
Path object which describes what area of a widget should remain visible. After defining bottom left apex 60 pixels above the bottom right apex, we end up with a figure we needed.
Now we just need to use our clipper with
In the design, this image is a bit darker, let’s try to achieve a similar color. To do that, we will use color blending:
Since I am not really good at recognizing colors, I will say that what we get is good enough.
. . .
I won’t put much attention to the header. I will use slightly different icons and possibly different font since it is not that important in the scope of the whole design. The top header will be a simple
Icons and a
Now we just need to include this widget in our stack and put it on top of the clipped image.
Full code for this stage can be found here.
. . .
3. Profile view
Similarly to the header view, let’s not put a lot of attention to this part. Simple row with a column, some paddings and we end up with this:
Unfortunately, I had no idea how to find the same image so I just used my own. Now let’s add it to the stack and see how it looks.
. . .
4. List header
We are getting closer to the actual content of the design. Let’s assume that the header and list are always below the image. Now we can define
buildBottomPart method which would return everything from the header below.
And, as always, we need to add this view to the stack:
. . .
5. List of items
First, let’s create a model class, which will represent tasks shown in the list. We can see that tasks have a name, category, time, color and since we can filter them, we can assume that they can have some completed flag. I will ignore the hangout task with small avatars.
Now we need to initialize a list of such tasks.
When it comes to the actual view, we can see, that there is a vertical line going through the whole screen and hiding behind the image. To create that line we will write the following method:
After adding it to the bottom of the stack (so as a first child), we will end up if nice vertical line going through the whole screen. Now let’s create a Widget representing a
Task. It could be a
StatelessWidget but we will create it stateful because we will need it later (EDIT: Actually, we won’t. I changed it to StatelessWidget later on).
Widget’s row contains 3 elements: a dot, which is a
Container with circular
Column with two
Texts and a trailing
Text showing Task’s time.
Now we just need to put it all together in a
We needed to wrap
Expanded because otherwise, we couldn’t place it inside a
At this moment we have a static list of tasks:
. . .
6. Animated items filtering
Now let’s try to get collapsing effect from design. First, we need to change
AnimatedList. This will allow us to easily add animations to our list. If you are interested in more details on how to use AnimatedList, I recommend this sample. So let’s update
AnimatedList gives us two main methods:
remove(index,builder). Since we can only pass the index, it is important to keep our data list with our view list synchronized. To do that, let’s add a
Most important aspects of this class are
Insert basically adds an item to both
List of Tasks as well as
RemoveAt does the reverse, however, we can pass a
builder that will build a Widget to be drawn in place of the removed item. For simplicity, we will leave a simple container there and we will come back to it later.
Now let’s change
MainPageState so that it uses listModel instead of a static list of tasks.
MainPageState, let’s add a possibility to change the filtering of our tasks. To do that we can add a bool field
showOnlyCompleted and change it when a user clicks on
How it works is that on every
FloatingActionButton tap, we iterate through all non-completed tasks (because completed tasks are shown either way) and then depending on
showOnlyCompleted value, we either insert them or remove them. It looks like this:
Ok, but where is the animation? In
AnimatedList‘s builder, we do have an animation, which is managed by
AnimatedList and can be passed to our row view. Let’s add
FadeTransition which will be responsible for shrinking and expanding rows depending on
Now we need to pass that transition in
Our animated list looks like this:
Let’s add minor improvement to durations based on widget’s position in the list.
We end up with this:
. . .
7. Animated Floating Action Button
Now it’s time to give our
FloatingActionButton an animation. Let’s start by creating a new class for it.
And let’s repace old Fab with new one:
Now let’s start with animating core of the fab. We can see two transitions here: first is changing Icon from filter icon to close icon, second is changing color from default pink to a darker one. Since there are synchronized we will only need one
AnimationController. From this point, clicking on Fab will not invoke
onClick method, it will only open and close Fab widget. Let’s start with changing color:
We created two animations.
AnimationController will be our main point of controlling animation.
ColorAnimation can be considered as a derivative of the controller that provides
Color value between pink and dark pink. Everything is wrapped inside
AnimatedBuilder which rebuilds view every time controller’s value changes.
Now let’s change the icon. We will use same
AnimationController but without any extra
Animation. On the design we can see, that icon is shrinking vertically and then expanding as a new one. To achieve that we will use
Transform widget, which will change only among the Y-axis. We also need to calculate the size factor since we cannot just pass the animation controller’s value.
It’s time to add an expanded background to our Fab. To do that we need to set its size to fixed values of the expanded state. That means we will also need to change the position of it in the stack. We will also wrap fab in a new stack which we will use later on.
Now let’s add a background. It will be a circle which changes its size depending on animation value.
We have also added
hiddenSize. If we just multiplied
_animationController.value it would technically work, but at the initial phase of expanding, the background would be hidden behind core fab. It would create an illusion of delay which we don’t want to happen. The solution is creating a minimal size of background to which it shrinks, that is
We’re almost done, now we need to add icons to the expanded state. First, let’s try to statically place the icons in the right position. Since there are positioned on the circle,
Transform.rotate seems like a good widget to use. For every icon, we will define an angle of rotation. Notice that we will use two rotations because after we place our icon, we need to rotate it back to default state so it won’t end up upside down.
Now let’s animate it! All we need to do is adapt the size of icon depending on animation’s value.
The last thing to is adding a listener on icon click. In
onPressed method we will pass this:
Let’s see how everything works together!
Founder of Fidev
Flutter enthusiast since Alpha release in 2017. Believes that sharing is caring, which lead him to start a technical blog dedicated fo Flutter in its early days. Loves to see beautiful designs become real apps and is willing to help make it happen. Enjoys sunny beaches far from home.
Join the newsletter!
Join the newsletter to keep track with latest posts and get my special Widget to animate views' entrances without any hassle for FREE!
Check out other posts!
Have you ever struggled to integrate card payments into your mobile Flutter app? If so, today is your lucky day! In this post, I present how to use Stripe Checkout in the Flutter app without any hassle!
Have you ever developed a widget or a page and you wanted to make sure it works correctly in different scenarios but then it turned out that you can’t just reproduce all the cases you want to cover? Framy may solve such problems!