How to animate old page during transition in flutter?

Viewed 1501

I made a custom transition for my iOS project, and now I want to move the project to Flutter. The transition is fading out the old page, and fading in the new one.

But I cannot achieve this by overriding the PageRoute.

I did some research on this:

There's a similar question Animate route that is going out / being replaced

From the accepted answer, I know there's a parameter 'secondaryAnimation' which may be useful to achieve it, but after trying to use the code from it, I still cannot animate the old page, all transitions have happened to the new page (the 'child' widget).

Can I get an 'old page' instance from the buildTransition method for animating? Or is there a better way to animate the old page?

Thanks!

2 Answers

I think that secondaryAnimation is used when transitioning to another page. So for your initial route, you'd have to specify the animation of it going away using secondaryAnimation and on your second page you use animation to animate how it appears.

It's a bit awkward that you have to use secondaryAnimation when creating the first route, because it means that it will be used for any transition away from that route. So, with PageRouteBuilder, you can't, for example, let the old page slide to the left when transitioning to page B but slide up when transitioning to page C.

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.

Related