I used the AnimatedBuilder class to animate the old page.
Here is my script.
DartPad
enum RouteType { push, pushReplacement }
typedef NavigatorRoute = void Function(Widget page, [RouteType type]);
class PageSwitcher extends StatefulWidget {
const PageSwitcher(
{Key? key,
required this.builder,
this.duration = const Duration(milliseconds: 500),
this.reverseDuration = const Duration(milliseconds: 500),
this.curve = Curves.linear,
this.onBegin,
this.onEnd})
: super(key: key);
final Duration duration;
final Duration reverseDuration;
final Curve curve;
final Widget Function(BuildContext context, NavigatorRoute route) builder;
final void Function(AnimationStatus status)? onBegin;
final void Function(AnimationStatus status)? onEnd;
@override
State<PageSwitcher> createState() => _PageSwitcherState();
}
class _PageSwitcherState extends State<PageSwitcher>
with TickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: widget.duration,
reverseDuration: widget.reverseDuration,
vsync: this,
);
bool _isDisposed = false;
Future<void> _forward() async {
if (widget.onBegin != null) {
widget.onBegin!(AnimationStatus.forward);
}
await _controller.forward();
if (widget.onEnd != null) {
widget.onEnd!(AnimationStatus.forward);
}
}
Future<void> _reverse() async {
if (widget.onBegin != null &&
_controller.status == AnimationStatus.completed) {
widget.onBegin!(AnimationStatus.reverse);
}
await _controller.reverse();
if (widget.onEnd != null) {
widget.onEnd!(AnimationStatus.reverse);
}
}
Route _createRoute(Widget page) {
return PageRouteBuilder(
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) =>
page,
transitionDuration: widget.duration,
reverseTransitionDuration: widget.reverseDuration,
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
if (!_isDisposed) {
if (animation.status == AnimationStatus.completed &&
_controller.status == AnimationStatus.dismissed) {
_forward();
} else if (animation.status == AnimationStatus.reverse &&
(_controller.status == AnimationStatus.completed ||
_controller.status == AnimationStatus.forward)) {
_reverse();
}
}
final v = Interval(0.0, 1.0, curve: widget.curve)
.transform(animation.value);
return Opacity(
opacity: Tween<double>(begin: 0.0, end: 1.0).transform(v),
child: Transform.scale(
scale: Tween<double>(begin: 0.95, end: 1.0).transform(v),
child: child,
),
);
});
}
void _push(Widget page) {
Navigator.of(context).push(_createRoute(page));
}
void _pushReplacement(Widget page) {
Navigator.of(context).pushReplacement(_createRoute(page));
}
void _route(Widget page, [RouteType type = RouteType.push]) {
switch (type) {
case RouteType.push:
_push(page);
break;
case RouteType.pushReplacement:
_pushReplacement(page);
break;
}
}
@override
void dispose() {
_isDisposed = true;
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
final v = Interval(0.0, 1.0, curve: widget.curve)
.transform(_controller.value);
return Transform.scale(
scale: Tween<double>(begin: 1.0, end: 1.3).transform(v),
child: widget.builder(context, _route),
);
},
);
}
}
You may also check this ZoomPageTransitionsBuilder class to change default transitions.