Yes, in Flutter, page transitions are defined at the route boundary—specifically, they are a property of the PageRoute (or ModalRoute) that you push onto the navigator. The transition animation is not a property of the page widget itself, but of the route that wraps it. When you call Navigator.push, the new route’s buildTransitions method returns the animated widget that slides, fades, or scales the new page in, and the previous route’s buildTransitions can also animate the old page out. This design keeps the page widget clean and makes the navigation stack responsible for all visual choreography.
Background: How Flutter’s Navigator Works
Flutter’s navigation is built around a Navigator widget that manages a stack of Route objects. Each route represents a screen, and the navigator decides which routes are visible and how they transition when the stack changes. The most common route subclass is MaterialPageRoute, which provides the platform-adaptive transition (e.g., Android’s fade-up or iOS’s slide-from-right). However, the transition is not hardcoded into the route; it’s implemented in the route’s buildTransitions method, which you can override to create custom animations. This separation—page content in a widget, animation in the route—is what enables the flexibility Flutter is known for.
How Transitions Are Defined at the Route Level
When you push a route, the navigator calls buildTransitions on both the incoming and outgoing routes (if the outgoing route is still visible). The incoming route’s buildTransitions typically wraps the new page in an AnimatedBuilder or uses Transition widgets like FadeTransition, SlideTransition, or ScaleTransition. The route’s animation property provides a value from 0.0 to 1.0 that drives these transitions. For example, a simple fade-in route would look like:
class FadeRoute extends PageRouteBuilder {
FadeRoute({required this.page});
final Widget page;
@override
Widget buildTransitions(...) {
return FadeTransition(opacity: animation, child: page);
}
}
Notice that the page itself is just a widget—it has no knowledge of the transition. The route is the boundary where animation logic lives.
Customizing Transitions: PageRouteBuilder and Theme
For most apps, you don’t need to write a custom route class. Flutter provides PageRouteBuilder, which lets you define transitions inline when pushing a route. For example:
Navigator.push(context, PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => NewPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(opacity: animation, child: child);
},
));
Alternatively, you can set a global transition theme in your app’s ThemeData via pageTransitionsTheme. This applies a consistent transition across all MaterialPageRoutes without changing every push call. For example, to use a custom ZoomPageTransitionsBuilder on all platforms, you’d set:
theme: ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: { TargetPlatform.android: ZoomPageTransitionsBuilder() },
),
),
This is the cleanest way to change the default transition app-wide.
Common Problems and Misconceptions
One frequent misconception is that you can set a transition on a Widget itself, like Page(transition: ...). That’s not how Flutter works—transitions are route-level. Another issue is that custom transitions sometimes cause the previous page to jump or disappear. This happens because the outgoing route’s secondaryAnimation is not used in the transition. To animate the old page out, you need to listen to the secondary animation in the outgoing route’s buildTransitions, which is only possible if you override the route class. Also, be careful with TransitionRoute vs PageRoute—the latter includes an opaque property. If your new route is not opaque, the previous route stays visible and you must handle both animations.
How to Choose the Right Transition
When deciding on a transition, consider the platform’s conventions and the user’s mental model. For a standard drill-down navigation, a slide-from-right (iOS) or fade-up (Android) is familiar. For modal dialogs or bottom sheets, a vertical slide or fade is natural. For full-screen image viewers, a scale or fade works well. Avoid over-animating—too many custom effects can feel jarring and slow. Also, respect the user’s “reduce motion” accessibility setting by using MediaQuery.of(context).disableAnimations to disable or shorten transitions when needed.
Performance and Best Practices
Transitions run on the UI thread, so keep them lightweight. Use AnimatedBuilder to rebuild only the parts that change, and avoid heavy layouts inside the transition builder. Prefer FadeTransition, SlideTransition, and ScaleTransition over manually animating with AnimatedContainer because they are more efficient. Also, remember that the transition duration is controlled by the route’s transitionDuration property. The default is 300ms for MaterialPageRoute, but you can override it. For a smooth user experience, keep durations between 200ms and 400ms—shorter feels snappy, longer feels sluggish.
Alternatives to Navigator.push
If you’re using named routes, you can still define custom transitions by using onGenerateRoute and returning a PageRouteBuilder. For more complex navigation patterns, the go_router package allows you to define transitions per route using CustomTransitionPage. However, the underlying mechanism is still the same: the transition is a property of the route, not the page. If you’re using a state management solution like Provider or Riverpod, it doesn’t affect transitions—they are purely a Navigator concern.
Frequently Asked Questions
Can I set a transition on a specific page without affecting others?
Yes, by using PageRouteBuilder when pushing that specific route. You can create a helper function that returns a PageRouteBuilder with your custom transition and use it only for that page. Other pages will use the default MaterialPageRoute or your theme’s global transition.
Why is my custom transition not animating the previous page out?
By default, the previous route is not rebuilt during the transition unless it’s visible. If your new route is opaque (which is the default for MaterialPageRoute), the previous page is hidden immediately. To animate the old page out, you need to make your new route non-opaque and use the secondaryAnimation in the old route’s buildTransitions. Overriding PageRoute and setting opaque = false is one way.
How do I disable all page transitions?
Set the pageTransitionsTheme to use a builder that returns the child without any animation, such as FadeUpwardsPageTransitionsBuilder with a zero duration, or create a custom builder that returns child directly. Alternatively, set transitionDuration to Duration.zero in your route, but the simplest is to use a custom PageTransitionsBuilder that ignores the animation.
Does the transition affect the back button behavior?
No, the transition is purely visual. The back button (or system back gesture) triggers a Navigator.pop, which reverses the transition automatically. The reverse animation uses the same buildTransitions with the animation running from 1.0 to 0.0, so it plays in reverse.
Can I use a Hero animation with a custom route transition?
Yes, Hero animations work independently of the route transition. They are triggered by the Hero widgets in the source and destination pages, and they animate during the route transition. You can combine a fade or slide route with a Hero animation—the Hero will fly between the two pages while the rest fades or slides.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.
More cards in this drawer
- Poco C51 Airtel Prepaid Locked Variant India?
- Jenkins vs GitHub Actions: Which CI/CD Fits Your Team?
- Turn Off Backup Encryption in iTunes?
- Campus Tech Innovation Hubs: What They Are
- Shared VPS Hosting: Is It Better?
- Disk Defrag Pro: What It Does and Whether You Need It