Filter Menu – UI Challenge

by May 11, 2018Flutter71 comments

In this post, I will do my first UI Challenge. As my goal I’ve picked this design by Anton Aheichanka from dribbble:

The design

Let’s get to it!

First, we need to decompose this view into few smaller units: 

  1. The clipped image on top
  2. Header
  3. Profile view
  4. List header
  5. List of items
  6. Animating items filtering
  7. Animated Floating Action Button

. . .

0. Starting point

As a starting point, we will create a simple app with a StackStack 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 Stack:

EDIT: Actually, I have changed it to BoxFit.cover. Also I wrapped it in Positioned.fill with bottom: null later on.

Now let’s add diagonal clipping. To do that, we need to create a CustomClipper.

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 ClipPath widget.

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. 🙂

The full code for this stage can be found here.

. . .

2. Header

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 Row with Icons and a Text.

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.

The full code for this stage can be found here.

. . .

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:

The full code for this stage can be found here.

. . .

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 boxDecoration, a Column with two Texts and a trailing Text showing Task’s time.

Now we just need to put it all together in a ListView:

We needed to wrap ListView inside Expanded because otherwise, we couldn’t place it inside a Column.

At this moment we have a static list of tasks:

The full code for this stage can be found here.

. . .

6. Animated items filtering

Now let’s try to get collapsing effect from design. First, we need to change ListView to 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 _buildTasksList method:

AnimatedList gives us two main methods: insert(index) and 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 ListModel class.

Most important aspects of this class are insert and removeAtInsert basically adds an item to both List of Tasks as well as AnimatedList. 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.

After adding ListModel to 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 FloatingActionButton:

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 SizeTransition and FadeTransition which will be responsible for shrinking and expanding rows depending on animation value.

Now we need to pass that transition in insert and removeAt methods.

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:

The full code for this stage can be found here.

. . .

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 expandedSize and _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 hiddenSize.

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 IconButton‘s onPressed method we will pass this:

Let’s see how everything works together!

Original design

My view

Ok, so there are some differences. 😀 Let’s say it’s close enough 🙂

The full code can be found here.

If you like this post or you think you could do something better, please leave a comment.

Cheers 🙂

Marcin Szałek

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!

Stripe Checkout in Flutter Web

Stripe Checkout in Flutter Web

Flutter Web is getting more mature every day. If you want to accept payments using Stripe Checkout in your Flutter web application, this article is just for you!

Stripe Checkout in mobile Flutter app

Stripe Checkout in mobile Flutter app

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!

Interacting with Widgets using Framy

Interacting with Widgets using Framy

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!

Share This

Share This

Share this post with your friends!