Flutter Route Animations
Picture this, you open an app, tap a button and another screen fades in…cool but boring. As a developer, you are always looking for ways to make your app stand out in the sea of millions. For those who are building their apps with Flutter, custom animations are not difficult. An AnimationController
paired with a few tweens and you’re equipped to rule the world.
Under the hood, Flutter uses Route Animations to handle screen transitions. The framework has two main types out routes, PageRoute
and PopupRoute
. As the name would suggest, page routes are mainly used for navigating from screen to screen while popup routes are used for handling dialogs and modals.
In this article, we will have a look at the various routes and classes used by the framework. Since PageRoute is widely used and pretty straightforward, we’ll build a small sample project using its sibling, PopupRoute.
To understand PopupRoute
we need to start at the very beginning. What even is a Route
?
Routing is at the core of all apps. Popups, pages/screens, dialogs all use routes in one form or another. In Flutter, there are four types of routes:
- Route
- OverlayRoute
- TransitionRoute
- Modal Route
- Popup Route
Route
Route
is the base class used by all routes in Flutter. It is an abstract class which defines an interface for interaction with the Navigator
. It contains properties for whether the page/screen is active and at the top of the navigator stack or inactive and at the bottom. Getters are also defined for retrieving the result when the route is "popped" by the Navigator
and the current list of overlay entries used by this route (more on the overlay stuff later).
Also defined in Route
are methods for handling rebuilds, state change, changes to the next and previous route in the stack and callbacks for when the route is popped.
Below shows a full list of the available methods and their functionality:
Overlay Route
Next, we move to OverlayRoute
. As the name suggests, an OverlayRoute
is a route that displays widgets in the Navigator
overlay, above the current route. The constructor of this class takes on an optional parameter, RouteSettings
which is passed to the superclass, Route
.
Included in this class are two additional properties, finishedWhenPopped
and overlayEntries
. finishedWhenPopped
returns a true when didPop
successfully removes or "pops" the route and it is disposed of by the Navigator
. Overlay entries, on the other hand, is a list of entries the route has placed on the Overlay
.
What is an Overlay
A section talking about OverlayRoute
would not be complete without mentioning Overlay
and OverlayEntry
. An overlay in Flutter is a Stateful
widget that contains a list of the onstage and offstage children. The class Overlay
takes an optional list of overlay entries, initialEntries
. If no entries are specified, an empty List
is automatically created. Also in this class is a static of
method for retrieving the closest instance of OverlayState
. This is useful since if you'd like to add an overlay to the screen, you'd need to call the insert
method located in the state class.
The real magic happens in the state class. Here, two very important methods exist, insert
and insertAll
. When the state is first created, the initialEntries
are added using the insertAll
method. As you might've guessed, this method takes an Iterable
of overlay entries. The elements in this Iterable
check whether they should be inserted above another entry (passed to insertAll
as an optional parameter) then inserted into the current list of entries at the appropriate index. The process is pretty much the same for the insert
method, only this time there is no need to loop over any list.
What is an Overlay Entry
At this point, you’ve probably seen the term “overlayEntry” quite a lot. Let’s take a few minutes to briefly have a look at it since it is the foundation of the Overlay class, after all, it is the class that is being inserted and removed everywhere.
An Overlay Entry is a class which contains a WidgetBuilder
and
Transition Route: Animations 🎉
What’s the one thing missing from routes at this point? Animations of course!
Transition route as the name implies, adds entrance and exit animations to routes. It creates and manages an AnimationController
which is used to drive the transition. The controller
, animation
and duration
are all exposed via getters.
Transition Route contains a Completer
which is used to check whether the transition is finished or if the overlay entries have been removed from the navigator's overlay. The future also completes once the animation has been dismissed after popped
was called. Both createAnimationController
and createAnimation
utilize this completer to ensure the route is not disposed of before carrying out their function.
The various animation statues are handled internally by the function _handleStatusChanged
. A completed animation changes the opaque
value of the first overlay entry to reflect that of the opaque
value passed by the user. Should the route be opaque, the route behind it will not be built in an attempt to save resources. Both reverse
and forward
statuses check the overlayEntries
to ensure they are not empty then set the opaque
value of the first overlay entry to false. This is done since while the route is being animated, the previous one will still be visible. The final animation status dismissed
checks to make sure the route is not active before calling navigator.finalizeRoute
to properly dispose and remove the route.
Modal Route
Implemented by PageRoute
and PopupRoute
, a modal route simply block all interaction with the previous route. That said, not all ModalRoute
is opaque. Popups such as dialog boxes and menus can overlap the previous route without hiding the content that is beneath it.
Modal routes also take a <T>
argument that is used as the return value of the route. Should there be no return value, it is recommended that you use void
as the return value.
Like the classes before it, ModalRoute
also exposes additional properties to the user. The ability to control how the route is dismissed, the label and color of the barrier, whether the route should maintain its state when inactive and the primary and secondary animations are just some of the noteworthy properties exposed.
Defined in the class ModalRoute
are the methods buildTransitions
and buildPage
. As the names imply, build transitions is used to animate the static content of the page. This method is called every time the route state changes. Since its primary purpose is for animations, you should avoid building static content in this method, buildPage
should be used instead since it would be more efficient.
Build Transitions takes the following properties:
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double>
secondaryAnimation,
Widget child,
)
Note: The child property passed to this function is the result/return value of buildPage
.
Build Page as mentioned earlier is used to build the static content of the page. The primary content of the route is built using this method. It is called when the route is first built and is rarely (if ever) called after that point. Unlike buildTransitions
, it is not automatically called when the state changes unless ModalRoute.of
is used.
The signature of buildPage
:
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
)
Putting it all together
We’ve covered a lot of theory in this article but the question remains, how does this all come together? How do I build something cool with this?
Here is a demo video of what we will be building:
Can you spot the transitions? In our example, we will be combining:
— Popup Routes
— Rotation Transitions
— Scale Transitions
0: Project Configuration 🛠
Let’s start by creating a new Flutter project, feel free to use any name you like. Once you’ve created a project, create an assets
directory and drag an image of your choice into the folder.
Note: Don’t forget to declare your asset in pubspec.yaml
. See here for detailed instructions.
1: Pages 📒
Before we get into any custom routes, let’s first create some pages for us to play with. For the purposes of this demo, I created a home page containing a ListTile
with the onTap
set to an empty function.
Home page code
The second page is also very basic, it uses an AnimatedPadding
to add a margin around the page content. We use the window’s viewInsets along with an extra padding of 30 pixels vertical and 24 pixels horizontal. As for the body of the page, a Material
is used to apply a background color and border-radius. Finally, we center our image on the page using an Image.asset
.
2: Time to get Nerdy 🤓
To create our custom route, create a class extending PopupRoute
. Once created, you will be prompted to override a few methods.
Here is an overview of the generated methods:
barrierColor
— Controls the background color used by the modal route. If this is left null, the barrier will be transparent.
barrierDissmissible
— Controls whether the route can be dismissed when the user taps the barrier.
barrierLabel
— If the barrier is dismissible, this label is by semantics for accessibility.
transitionDuration
— The length of time for the route animation
buildPage
— This method is used to build the primary contents of the route. This method should not be used to build animations.
buildTransitions
— This method is used to build the route’s animation. The parameter “child” is the output of the method buildPage
.
Hopefully, this helps you understand the role of the different methods and properties. If you are following along, customize and change the values of these, the results are always surprising :).
Now that we’ve created the class, change the functions and add some variables. Let’s create a final member variable builder
of type WidgetBuilder
. This will be passed to the constructor of the class. Next, call this function in buildPage.
This handles building the content of the page. Time to add animations and make everything fancy ✨. Let’s move on to buildTransitions
. As previously mentioned, this method handles building animations for your route. The child
parameter passed to this function represents the output of buildPage
. Feel free to customize and mix transitions, I am going to keep things boring and use a RotationTransition
and ScaleTransition
.
Great! Run the project and build the test app. If all is well, you should have a layout similar to mine.
Change the color, experiment with secondary animations, the possibilities are limitless.
Conclusion 💭
Routing and navigation are very large topics. This article focused on animations, a small piece of a much larger puzzle. For those who’d like to learn a bit more on the topic, here are a few resources you can check out:
- The Flutter Whisperer (Simon Lightfoot) and Mariano Zorrilla’s talk at Flutter NYC — Coming Soon
2. Clean Navigation in Flutter Using Generated Routes
4. API docs
The code for this article can be found on my Github.
I am excited to see what everyone builds, tag me on twitter with your animated creations!
— Nash