Advanced transitions – UI Tickets Challenge
As developers, we have always get into situations where the designers require some fancy transitions which look awesome on the design but are extremely difficult to implement. Being a Flutter developer makes it different, makes it fun because we have tools to do it! To prove it in this post, I will continue implementing awesome Buy Tickets design by Dldp and add bottom sheet transitions! Let’s do it!
In the previous post, we have created a parallax effect for the cards and created a simple bottom sheet. We will leave the page content untouched, but we will need to remove the bottom sheet and write from scratch. What we need is to have a
Stack widget in order to have full control over how the
ExhibitionBottomSheet will be placed.
And let’s have a simple blue sheet so far:
Positioned ensures that the element sheet is always expanded horizontally, it is aligned to bottom and has a proper height (which will be changed later on). The
Container so far specifies only padding and decoration, which gives us nice rounded corners on top and accurate background color.
. . .
Expanding the sheet
The sheet’s height will be defined by
AnimationController. We will wrap the whole widget in
AnimatedBuilder, because almost every widget in that view will depend on the controller’s value and will need to be rebuilt whenever that value changes. To expand the sheet, we can use
AnimatedController.flingmethod, which will basically snap the sheet in a given direction. We can call this method by wrapping the sheet inside the
GestureDetector and providing
You can also notice we have added a
lerp function. This function is just a helper to interpolate a double value between its min and max value based on the controller’s progress. We could probably use
Tween instead as it does a similar thing but I find lerp function suiting well here. We can see how the tap causes the sheet transitions between small and big one:
. . .
Dragging the sheet
At this moment the user can tap to open the sheet but we also need to provide an option to hold the sheet and manually move it to the top. There are two things we have to keep in mind. The first one is to update the sheet’s height alongside drag updates. The second one is to finish the animation once the user has finished the gesture (he or she can just snap and expect the sheet to get expanded).
Now when the user drags the sheet, we can update controller’s value, also when the user ends the drag gesture, we can check it’s velocity and decide whether we want to complete the animation or dismiss it.
. . .
Icon and title transitions
I think it’s time to put some content inside our Container. Let’s start with the icon as it’s the easiest one. Since we will need control of every Widget’s position, we will place them inside the
Stack. This way we can wrap every Widget in
Positioned and specify its position. For the
MenuButton it will look like this:
Now let’s add the
SheetHeader. The widget itself will require a fontSize and topMargin in the constructor, as those parameters will change alongside the animation’s state.
To pass them, we will create new getters which will depend on the animationController’s value. We are going to need
What it means is that for example
headerFontSize starts with the value of
14 but the bigger the sheet is, the bigger the font size gets finishing at
24 on a fully expanded sheet.
Now we can see how the icon is placed in the same position but header changes based on transition’s progress:
. . .
It’s time for the main attractions, which are the icons. First, we need to create a model and list of events which will be displayed in the sheet.
Now let’s think about what we need to do to change the icons from horizontally oriented small ones to vertical big ones. Since we are already using
Positioned we can just adapt icons left and top alignment based on the controller’s value (similarly to the top margin in the title). The only tricky part is to remember, that we need to take event’s index into account so that every next icon is placed more into the right (horizontally) or more into the bottom (vertically). We can also modify the icon’s size and corner rounding. The getter methods should look like this:
It’s that simple! Knowing how much space should widgets take, we can calculate the exact position we want them in. And knowing the exact position of the widget, we can pass
Positioned to get the effect we wanted:
. . .
The last part is to add the ticket details once the animation is completed. Since we know the position of icons, we can use it also for the expanded items — this will be easy. Theoretically, we could just decide, that if
_controller.status == AnimationStatus.completed, then show the details, otherwise don’t do it. Such an approach would make us lose the opportunity to add some nice fade animations in that process. Instead, we will use
AnimatedOpacity so that the expanded item will nicely fade in and fade out when it’s supposed to be displayed or hidden. This is my approach, not sure if it’s the best but at least it’s simple, it is possible that linking the item’s opacity with the controller’s value can provide a better effect. As always, it’s up to you.
The widget is quite long, but it’s mostly just organizing and displaying data — nothing exciting. Depending on what we pass in
isVisible parameter, it will cause widget transitions between visible and invisible. If you dive in closely, you can also notice that
ExpandedEventItem has margin-left equal to the height. It’s because this widget is meant to be placed under the icon widget. This way we make space for the icon to provide an illusion that it’s a part of the details card.
Now we need to add those details to the main stack before the icons, so they will be rendered under them. We will also modify rounding corners to match the designs.
. . .
And that’s it!
Now before we compare the results, let me point out that in this post I focused only on implementing the effects from the design. If you would like to use it in your own app, I would advise adding limits on how many items you display in horizontal view (as they would go over the menu button). Also, I would replace the whole view with the
ListViewwhen the sheet is fully open. Right now those elements are not scrollable but I guess they should be.
I hope you enjoyed going through the process with me and that you feel ready to implement it in your app as well.
If you find this post helpful, please share it with others, so they can learn too!
You can find the full code on the GitHub repository (feel free to leave a Star if you like it).
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!
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.
Check out other posts!
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!
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!