Flutter: Page Transition for Both Entering and Leaving Pages

Tsung-Wei Hsu
4 min readApr 17, 2023

--

Page Transition is undoubtedly one of the fundamentals in app development, and Flutter already offers a great default transition that aligns aesthetically with its platforms:

Left for Android and right for iOS

But what if you want to customize it as a part of your app design language, especially when the transitions take place in the context of a nested navigation where the standard one might not fit nicely?
Let’s get it programmed! 🙌🏼

If you’ve already created some pages with navigations, onGenerateRoute should be familiar to you, in which target pages are defined. Below is a simple example:

// when using root navigation
MaterialApp(
title: 'Demo App',
onGenerateRoute: _onGenerateRoute,
...,
);

// when using nested navigation
Navigator(
onGenerateRoute: _onGenerateRoute,
...,
)

Route _onGenerateRoute(RouteSettings settings) {
final page = _buildPage(settings.name);
return PageRouteBuilder(
pageBuilder: (context, anim, secAnim) => page,
transitionsBuilder: _buildTransitions,
);
}

Widget _buildPage(name) {
switch (settings.name) {
case '/next':
return NextPage();
default:
return HomePage();
}
}

There are several route builders such as MaterialPageRoute()and CupertinoPageRoute(), but here we use PageRouteBuilder() with a lower level of configurations. The param transitionsBuilder within is where we customize both entering and leaving animations.

Now, take a closer look at the builder itself which provides four inputs:
a build context, a primary animation, a secondary animation, and a child.

return PageRouteBuilder(
pageBuilder: (context, anim, secAnim) => page,
transitionsBuilder: (context, anim, secAnim, child) {
// we start here
},
);
  • The primaryAnimation, also the most mentioned one, stands for the entering page, i.e. the animation of the next page.
  • The secondaryAnimation is the opposite for the leaving page which however is significantly less documented or described elsewhere. Here, we will tackle them both.

Starting with the entering page, two tweens are defined separately for the slide and the fade transitions. Attach both tweens to the primary animation that makes the next page slide from the right side by Offset(1, 0) with the opacity from 0.

return PageRouteBuilder(
transitionsBuilder: (context, anim, secAnim, child) {
// tween for the value tranistion of opacity
final fadeInTween = Tween<double>(begin: 0, end: 1);
// tween for the value tranistion of position
final slideInTween = Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
);
// make the next page slide into the screen with opicity
return FadeTransition(
opacity: anim.drive(fadeInTween),
child: SlideTransition(
position: anim.drive(slideInTween),
child: child,
),
);
},
);

It is worth noting that the wrap order does not affect the transitions, and their duration can be defined in the PageRouteBuilder() with the param transitionDuration. Your result should look similar to this:

Sliding in and out of the next page with a fade effect.

Next, we want animate the leaving page as well by creating the identical type of tweens but in a different direction, i.e. the previous page slides out to the left side by Offset(-1, 0) with the opacity reducing to 0.

final slideOutTween = Tween<Offset>(
begin: Offset.zero,
end: const Offset(-1, 0),
);
final fadeOutTween = Tween<double>(begin: 1, end: 0);
return FadeTransition(
opacity: secAnim.drive(fadeOutTween),
child: SlideTransition(
position: secAnim.drive(slideOutTween),
child: FadeTransition(
opacity: anim.drive(fadeInTween),
child: SlideTransition(
position: anim.drive(slideInTween),
child: child,
),
),
),
);

Same as its counterpart, the newly created tweens are applied to the secondary animation, that enables the transition of the previous page. Now, the page route animates both pages at the same time:

A complete page transition for both entering and leaving pages

As described above, the wrapping order of the transitions does not influence the overall animation, but a grouping order, e.g. the secondary followed by the primary, might still be convenient for the readability.

To sum it up, with transitionsBuilder under PageRouteBuilder(), we understand that the primary animation controls the transition of the entering page similarly for the secondary in the relation to the leaving page. By respectively injecting them into the transition widgets, a complete transition of both pages is achieved.

Since the ground animations are exposed in the builder, the customization can surely go further with the additions such as curved animations, gesture manipulations, etc. The ability to swipe back to the previous page is certainly a useful extension, but it’s a topic of another day. Stay tuned! 📡

--

--

Tsung-Wei Hsu

frontend | web and mobile | apps | programming and making coffee from Frankfurt, Germany ☕️