0. Starting point

1. AppBar and buttons

Since our AppBar is a bit expanded and all the views are going to be written on top of it, all the views in the application will be based on a Stack widget, which allows us to easily put widgets on top of each other. Now let’s create an AirAsiaBar widget.

We have created a simple stack containing a Container, which will be our background as well as transparent `AppBar` which is placed on top of the container. The height of the container has been extracted because further on the bar’s height changes a little and we would like to be able to reuse the same component. You can also notice a custom FontFamily. I have downloaded it from here and added it to `pubspec.yaml`. I know it is not exactly the same but I’d say it’s close enough 🙂

I don’t think there is anything worth explaining here, maybe except the fact that the button is wrapped in Expanded. I did only because I didn’t want to do it multiple times when I use the button. If I would actually want to create a reusable widget, I would advise against making it Expanded. Now let’s add those buttons to the HomePage:

As you can see, I placed _buildButtonsRow inside of a Column which was inside of a Positioned. The Positioned widget is needed because we need to manually put all the content under the AirAsia label but on top of the AppBar background. A Column will be needed later so we can put a card with content under the buttons. At this moment our app looks like this:

2. Initial input

Now, we need to create a card that will contain most of our views. Even though it might seem pretty straightforward there are some problems that need to be solved during this part but first let’s see the code:

You can notice that in the design there is a gray line behind the tab indicator. To achieve this effect, we have to use a Stack in which we will place a small Container under the actual TabBar. You can see it in _buildTabBar() method.
Harder part comes with the Input view. It is worth remembering that whenever you are using any TextFields in Flutter you almost always want to wrap them inside some sort of scrollable views like CustomScrollView or ListView, so that when the keyboard appears your layout won’t get disrupted. However, in our example, we also want to place a FloatingActionButton at the bottom of the view. Technically we could place it under the ScrollView but that would cause it to be always there even if there are more fields to be scrolled to. We could also place it just inside the ScrollView, but then it wouldn’t be aligned to the bottom if there was a space for it.

The solution to that problem is using the following combination of widgets: LayoutBuilder which will provide us access to BoxContstraints, then we use SingleChildScrollView with ConstrainedBox and we pass maximum height we want our layout to have (obtained from constraints). In the end, we need IntrinsicHeight so that our view will fill as much space as it can but it will be able to render it in `ScrollView`. Now we can have a Fab that is aligned to the bottom but will also be scrollable if more content occurs.

3. Plane resize and travel

Let’s settle what is about to happen now. After a user clicks on a floating action button, the view immediately changes to another tab, however, the TabBar names remain the same. Then there is a short delay after which plane starts to go forward with a simultaneous change of names in tabs (I will ignore that small animation of the names).

Let’s start with resizing animation. First, we will create a new Widget called PriceTab (to be honest I am not sure why it is a price 😀 ). We will place everything inside a Stack and we will mostly operate on Positioned widget to put widgets where we want them. In general, it is not trivial to get Widget’s size before it gets rendered but luckily we have wrapped our tab inside of LayoutBuilder so we have access to view constraints and therefore to Widget’s height. This way we will be able to calculate the widget’s padding top as followed:

So what we’re doing is creating in onInit() method an _planeSizeAnimationController which is a parent of actual size animation _planeSizeAnimation. This animation will scale from 60 to 36 which is the actual size we would like to achieve. We pass it to the AnimatedPlaneIcon widget which will be rebuilt on every animation change.

We have added an Animation to that controller, to get access to nice curve instead of a linear one.

We have changed _planeTopPadding getter so that it is dependent on _planeTravelAnimation.

We have added an AnimatedBuilder which will rebuild the plane icon according to Animation’s value.

The last thing for that part is to add a trail and change the names in the tabs. When it comes to the trail, we just need to add it to _buildPlane() method. For now, let’s keep the length of it as 240.0.

Regarding update of labels, I won’t describe it here but you can check it out in the full code for this part.

EDIT: There was missing - _minPlanePaddingTop in the _maxPlaneTopPadding. Add it to move the plane a bit from the bottom edge.

4. Dots travel

In order to place dots, we need to know their positions. Obviously, they have to match cards on the sides. At this moment let’s assume that we have 4 cards, each will have a height of 80.0. We can also assume that those cards will overlap a bit (we will place cards in distance of 0.8*80.0).

Let me explain what happened. We created a list of flight stops which at this moment will be integers. We also introduced a fixed height of one stop which is 80.0. We created a _dotsAnimationController which will control all dots animations. The interesting part is how every dot is animating. Technically we could create 4 animation controllers each to every dot but there is a better solution to that. We can use an Interval curve.

Interval is an animation curve, that allows us to specify at which moment of whole animation (defined by AnimationController) we want a single animation to start. For example, if we specified in the controller the duration to 10 seconds and we created animation with an interval with start equal to 0.3 and end equal to 0.7 then our animation will start with 3-second delay and end after 4 seconds. This way we can have multiple animations overlapping with each other controller by one controller. We defined our animation to take 0.4 of time declared in a controller and each animation starts 0.2 after the previous one. We also defined what is the original and final position to every dot based on its index and card height. We have added all animations to the list, so that we can pass them to AnimatedDot widget which will handle drawing a dot.

5. Flight stop card view

Theoretically, this view is pretty simple and can be done with a proper usage of Rows and Columns. However, if we look closely at the design, we can see that every text in the card shows up in a slightly different way. To achieve that we need to animate each text separately, so we need to know each text’s position. That’s why we will use stack here.

The first thing worth mentioning is isLeft flag. Since the card can be placed on both sides, it will be built differently so we want to pass that information to it. The widget has specified height and width fields which are describing actual card’s dimensions. However, it is hard to say how big the actual widget will be. To get the maximum width of the widget, we are using render box’s constraints. Even though this approach might seem easy, it cannot be always used because during the first build, the framework doesn’t actually know those constraints yet. Luckily we will animate this view and on the first build it will have zero size, so we can get out with this.

Last thing worth mentioning are those buildMargin methods. All the texts in the card are either aligned top, right, bottom, left or center of the card depending on the text and if the widget isLeft. Those methods will be developed in the next section. For now, it’s important that they work 🙂

What we’ve done is depending on stop’s index we add an Expanded widget on the left or on the right of the card. Having the card also wrapped inside the Expanded we can make sure that it will take only half of the width.

6. Flight Stop Card animations

Ok, now it’s time to animate those cards! In order to do so, we will use a single AnimationController with multiple Interval animations just like with dots. We will also update getMargin methods so that they will be dependent on animation’s value.

We created an animation for each text we want to display. Every animation has slightly different interval values so that they create a feeling of being independent of each other. Also, we pass the parameter to ElasticOutCurve which is the actual curve of texts’ animations. We do it to decrease the bouncing effect. I won’t describe how the getMargin methods changed, they just work :). Each text widget is now passing animationValue to those methods as well as to its own font size. We have also added a public method runAnimation so that parent can decide when the card should be animated.

In onInit() method we create GlobalKeys which we later pass to FlightStopCards so that we have access to the state and we can start the animations. We also added a FloatingActionButton so that whole view will be completed. This fab’s animation will start just after last card animation’s start. This time, we use Future.forEach to run a delayed animation start for each card. This is how it looks like:

Unfortunately, using `ClipPath` was also cutting off the shadow. In order to mitigate it a bit I added a material with a very light shadow and then I clipped it with a smaller radius (If you have a better idea, please share! 🙂 ). This is what we got:

8. Flight ticket animations

This time we will use a lazier approach. To animate cards coming from the bottom, we can simply add a Translation from a big number to 0. I wouldn’t say it’s a perfect solution but it’s simple and it works.

Thanks so much for presenting this very instructive lesson on using Flutter, and for showing it can be used to build complex apps. I’m new to Flutter and plan to start building a basic Business Directory app using flutter. Can you give me any advice about how to get started, especially on the initial design? Do you provide app development services?

Hi Gil,
When it comes to learning Flutter, I think that at this moment I would start with Udacity courses. I would also read about architecture design patterns like Redux and BLoC to have that in mind from start.

When it comes to design, I’m far from being an expert but what I would do is also start with Google mobile design and rapid prototyping courses on Udacity although this is just an idea since I didn’t do them so I can’t tell you they’re worth it.

Generally, I do provide such services but at this moment I’m taking a break so I wouldn’t count on me 😉

Thank you for the good flutter examples. I am new to flutter so I had a hard time finding good UI examples with complete and organized code. I will continuously visit your website if you will continue posting! Thanks for sharing. Someday I hope to post good flutter codes for others to reference as you did.

Hi, nice tutorial you write there. I have a question about the constructor. Why do you have to call the parent constructor and passing the key value to the parent. I mean do i have to pass the key to parent class for every widget that i created and like what does it do behind the scene. Thanks anyway. You rocks!

Well, to be honest, the answer to the question why I do it is that IntelliJ IDE adds super(key) call to constructor by itself. You don’t have add keys if you don’t have a specific need for that. Emily Fortuna explains it well 🙂 https://www.youtube.com/watch?v=kn0EOS-ZiIc

First of thanks for great tutorial , you have done amazing things .One thing I want to ask here is that How to show different layout when user select a train or bus tab. Normally we use tabs with Scaffold ,appBar and TabBarView.Now I am not getting where to define TabBarView where I can tell default controller that I have three different view for air, train and bus tabs.Thanks.

About me

Hi! My name is Marcin, I study Computer Science at Lodz University of Technology in Poland. On this blog I document some of development I do in my free time with belief that someone can get value out of this.