Level up Flutter page transition: choreographing animations across screens

Shian Poon
UX Collective
Published in
7 min readJul 20, 2019

Shared elements transition is the go-to choice to spice up default page transition. With Flutter, we can simply achieve this using Hero widget with just few additional lines of code.

But, little did we know that we can create slick page transition effects by carefully chaining each core component together!

Today, we will explore some neat screen transitions/animations through creating an interesting UI design I came across in Dribbble (Credited to Igor S.).

Beaches App, animation by Igor S. Check out his works in Dribbble.

The core mechanics of the screen transitions involve:

  1. “Persistent” Appbar across screens
  2. Tweening text size with Hero across screens
  3. Choreographing animations across screens with Navigator

Here’s a sneak peek of the end result.

Malaysia Beaches App UI, Made with Flutter

I will not cover how to layout the general structure of the screens. But here’s a summary of what each screen comprised of:

  • Main Screen: Scaffold with AppBar and ListView (consisted of header and list items).
  • Detail Screen: Scaffold with AppBar and ListView (consisted of header, stack to arrange the image & title layout, Column to wrap description & icons).

1. “Persistent” AppBar across screens

Usually we use MaterialPageRoute/CupertinoPageRoute to navigate to a new screen, depending on the platform we targeted.

We are granted a Fade Upwards Page Transition by default. But this is not what we wanted. AppBar ain’t “persistent”!

Default Page Transition using MaterialPageRoute

We can achieve the desired “persistent” AppBar effect by customizing our own PageRoute. Rather, creating an illusion that the AppBar is “persistent”.

First, we extend PageRoute and name it FadePageRoute.

To speed up, you can keep the implementation of MaterialPageRoute<T>. The only critical methods/properties we need to pay attention to are:

  • buildTransition method: This method defines how the next screen shows up and leaves the screen. We will create a separate transition FadeInPageTransitionto fit our needs.
  • transitionDuration properties: You can control the duration it takes to complete the screen transitions.

In FadeInPageTransition, we take few parameters in constructor:

  • Animation<double> routeAnimation: Animation which will be used during route transition. It plays from 0.0 to 1.0 duringNavigator.push, and 1.0 to 0.0 duringNavigatorNavigastor.pop.
  • Widget child: In short, our next screen DetailPage().

We then wrap our child with FadeTransition, taking an opacity animation with respect to routeAnimation to create a fade in effect.

And voilà, we created an effect as though the AppBar is “persistent” across screens.

This is also why I insisted to wrap quotation marks around the word persistent. If you can’t create genuine static AppBar, you trick their eyes instead (evil grin).

Fade In Page Transition

2. Tweening text size with Hero across screens

We will use Hero widget for shared elements transition.

It’s pretty straightforward, just wrap the widget with Hero and assigned same tag across screens. The framework will complete the remaining heavy-lifting for you. Done and dusted!

Hero mapped between two screens
Glitchy title is glitchy

Sadly, the result is kinda glitchy for Text widget. This is not okay!

In fact, this is the expected behavior with the default implementation of Hero widget.

Slightly in-depth explanation of why this could happen:

  1. Hero computes the size of source hero widget and size of destination hero widget.
  2. Hero creates RectTween to animate the Widget from size of source to the size of destination.
  3. Hero animates the destination hero widget from source location and size to where it supposed to be in the next screen (which is why we see big text appears immediately during enter transition, and it slowly enlarges to the size of its destination).
Source: Hero Animations @ flutter.dev

In short, the default Hero widget is not designed to animate text size and widget size during screen transition in the first place. But it offers a way for us to achieve the desired effect, through flightShuttleBuilder.

flightShuttleBuilder defines the widget when it “fly” across screen. By default, if nothing is provided for flightShuttleBuilder, Hero automatically uses our destination hero widget to fly across the screens.

To solve this, we will need to customized our own flightShuttleBuilder, providing an animated Text widget in the Hero constructor.

We will focus on Title (highlighted in green in the previous image) for this example.

The Title widget is shared across screens, only varied with different font sizes according to the requirements (static | animating | enlarged on main screen | shrunk on detail screen). Therefore, we need to define the state to choreograph each case accordingly.

First, create an enum ViewState as a mean to choreograph the widget.

Second, we create a StatefulWidget for our Title that takes in ViewState as parameter, so it behaves accordingly with theViewState passed in.

Third, we define how the widget should behave according to each ViewState. I defined the animation logics in initState because I wanted the widget to animate immediately when it is instantiated.

Lastly, create your flightShuttleBuilder that emits desired ViewState according to the HeroFlightDirection. Therefore, when the shared elements transition is triggered, our Text widget will fly to its destination and update its text size concurrently.

This is it. We apply the same logic to the app header and icon.

Hooray! The Title is sized properly with the transition now!

Hero transition with text size animation

P.S.: Alternatively, you can also pass in animation from flightShuttleBuilder as parameters to the animated StatefulWidget, then use animation.drive(yourTween) to synchronize the animations. I supposed this is a cleaner method to achieve the same effect. Please feel free to share your solution with me.

3. Choreographing animations across screens with Navigator

If you look at the AppBar carefully, the hamburger menu icon animates to back icon on entering into new screen, and vice versa.

We can animate the AnimatedIcon simply using a pre-defined animation, and call it before pushing new screen.

Easy peasy lemon squeezy!

Animated hamburger icon on Navigation

OH WAIT, the AnimatedIcon doesn’t animate back to hamburger when the detail screen pops! ΣΣ(゚Д゚;)

AnimatedIcon doesn’t animate back to animate icon when the screen pops

How do we choreograph animation to get back to its initial state when screen pops? And if possible, can we expand the trick to other use cases?

ValueNotifier to the rescue!

We need to keep a variable that hold information across screens. The ValueNotifier wrapping that variable will then react according to the variable whenever it is updated.

First, we define a variable bool returnFromDetailPage as a trigger when we enter a new route or return from a route. Wrap the variable in ValueNotifier so that we can listen to the variable when it updates according to the navigation.

Second, when we pop the screen on top of the stack, we return a value to the ValueNotifier. The ValueNotifier will then trigger its listener to animate accordingly.

Here we go! Our menu iconAppBar animates as desired.

Animated AppBar on page transition
The completed screen transition as per design (though not 100% alike :X)

You can apply the same logic to trigger some fancy animations tailored to the data returned from other screen. Sweet!

P.S.: Alternatively, I hypothesized we can achieve the same effect by overriding buildTransitions method in PageRoute<T>, then utilize the secondaryAnimation parameter from the function to choreograph the desired animation. Yet to be proven but I believe it works.

That’s all. We translated the UI design into a fully functional Flutter app (though not 100% alike).

The complete source code is hosted on GitHub.

Hope you found this interesting. Feel free to leave a comment and your thoughts!

Caveat: There are always better ways to achieve the similar/better results. If you have or found one, please share it to me and other readers. That’s the beauty of programming, isn’t it? :)

If you liked this article and my work, hit 👏 and connect with me on Facebook, Twitter, Github and LinkedIn.

You can also support me on Patreon, consider buying me a cup of coffee. ☕

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Written by Shian Poon

A white-collar on the day, a developer in the night. Flutter | Android | Engineering

Responses (3)

Write a response