Flutter Hero Page Route

Create Hero-like Page Route Transitions in Flutter

Stanislav Sopov
3 min readOct 27, 2021
Photo by Alvaro Reyes on Unsplash

Update

This article has been updated. See the new version here or skip to see the code.

Intro

This article will help you build a smooth Hero transition from a FloatingActionButton to another page while gaining full control of the route transition animation.

Getting Started

To run the project open the iPhone simulator and run flutter run.

This article assumes basic knowledge of Flutter and Dart.

You can find the complete project here.

Route Transition Basics

To navigate to another route, we’d normally use Navigator.push() passing either a MaterialPageRoute, which uses a platform-specific animation, or a PageRouteBuilder, which allows more refined control of how page transition takes place.

In this case, however, we want to leverage Hero animations to create a page transition from a FloatingActionButton while retaining full control of the animation timing and duration. We also want the button to morph into the new page instead of the page just popping up on screen.

Furthermore, we want to encapsulate all this logic in a separate widget class so that when a new route is pushed onto the navigation stack the code looks as concise as possible.

Hero Page Route Widget

At the top level our code looks like this:

Navigator.of(context).push(
HeroPageRoute(
tag: 'Hero Page',
child: HeroPage(),
)
);

Let’s have a look at what happens inside HeroPageRoute.

HeroPageRoute extends PageRouteBuilder to give it control over transition duration and animation. The widget returns a Hero with a custom transition tween and a child widget that uses the PageRouteBuilder's animation controller to animate the new page:

class HeroPageRoute extends PageRouteBuilder {
final String tag;
final Widget child;
HeroPageRoute({
required this.tag,
required this.child,
}) : super(
transitionDuration: Duration(milliseconds: 1000),
reverseTransitionDuration: Duration(milliseconds: 1000),
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return Hero(
tag: tag,
createRectTween: (Rect? begin, Rect? end) {
return CurvedRectArcTween(begin: begin, end: end);
},
child: PageRouteTransition(
child: child,
animation: animation,
),
);
},
);
}

Hero's createRectTween param allows you to define a custom transition tween. In this case we don't want to completely replace the default MaterialRectArcTween, but only control the transition timing using easing Curves. CurvedRectArcTween extends MaterialRectArcTween and overrides its parent class lerp method:

class CurvedRectArcTween extends MaterialRectArcTween {
CurvedRectArcTween({
Rect? begin,
Rect? end,
}) : super(begin: begin, end: end);
@override
Rect lerp(double t) {
Cubic easeInOut = Cubic(0.42, 0.0, 0.58, 1.0);
double curvedT = easeInOut.transform(t);
return super.lerp(curvedT);
}
}

PageRouteTransition creates a page transition using the animation controller provided by PageRouteBuilder. It starts with the FloatingActionButton's border radius, color and elevation, and transitions to a rectangle page. It extends AnimatedWidget, and returns a Material, which perfectly suits this purpose:

class PageRouteTransition extends AnimatedWidget {
const PageRouteTransition({
Key? key,
required this.child,
required this.animation,
}) : super(key: key, listenable: animation);
final Widget child;
final Animation<double> animation;
static final opacityTween = Tween<double>(begin: 0.0, end: 1.0);
static final elevationTween = Tween<double>(begin: 6.0, end: 0.0);
static final borderRadiusTween = BorderRadiusTween(
begin: BorderRadius.circular(100.0),
end: BorderRadius.zero,
);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Material(
color: Colors.blue,
clipBehavior: Clip.antiAlias,
elevation: elevationTween.evaluate(animation),
borderRadius: borderRadiusTween.evaluate(animation),
child: Opacity(
opacity: opacityTween.evaluate(animation),
child: child,
),
);
}
}

Note that Scaffold will automatically add a back button to the AppBar when a new route is pushed onto the navigation stack, so we don't have to handle Navigator.pop() here.

Summary

One of the best features of Flutter is the ability to extend existing widgets (in contrast to React, which discourages inheritance in favour of composition). Flutter’s comprehensive documentation and tutorials make it easy to find the code you can build on.

--

--