Sina Seirafi
Sina Seirafi

Reputation: 2363

Flutter | How to animate a widget upon navigation?

How to animate a widget based on navigation?

For when it is first created, we can use animationController.forward(); in the initState.

But what about the other transitions:

Upvotes: 0

Views: 330

Answers (1)

Sina Seirafi
Sina Seirafi

Reputation: 2363

Use RouteObserver.

final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();

then add this to root materialApp widget :

MaterialApp(
      theme: ThemeData(),
      navigatorObservers: [routeObserver],
) 

(Code above is taken from here)

Then you can control your AnimationController for any of the items above.

My implementation

Create a RouteAwareAnimationController widget and pass the controller and child to it.

Main Widget build:

return RouteAwareAnimationController(
      controller: controller,
      child: SizeTransition( // Or any other animation that's connected to the controller
    

RouteAwareAnimation widget

class RouteAwareAnimationController extends StatefulWidget {
  const RouteAwareAnimationController({
    Key? key,
    required this.controller,
    required this.child,
  }) : super(key: key);

  final AnimationController controller;
  final Widget child;

  @override
  State<RouteAwareAnimationController> createState() => _RouteAwareAnimationControllerState();
}

class _RouteAwareAnimationControllerState extends State<RouteAwareAnimationController>
    with RouteAware {
  AnimationController get controller => widget.controller;

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    routeObserver.subscribe(
        this, ModalRoute.of(context)! as PageRoute<dynamic>);
  }

  @override
  // page push, same as using this in initState
  void didPush() => controller.forward(); 

  @override
  // when this page is poped
  void didPop() => controller.reverse();

  @override
  // when next page is pushed
  void didPushNext() => controller.reverse();

  @override
  // when next page is poped
  void didPopNext() => controller.forward();

  @override
  void dispose() {
    routeObserver.unsubscribe(this);

    super.dispose();
  }
}

Feel free to customize it based on your needs, or pass bool values to it to enable or disable certain actions.

A more general solution

Use a more general RouteAwareWidget, so that you can pass a function for each specific navigation event.

It might look like this:

class RouteAwareWidget extends StatefulWidget {
  const RouteAwareWidget({
    Key? key,
    required this.child,
    this.onPush,
    this.onPop,
    this.onPushNext,
    this.onPopNext,
  }) : super(key: key);

  final Widget child;

  /// This function will be called when this page is pushed, aka initState
  final Function? onPush;

  /// This function will be called when this page is poped
  final Function? onPop;

  /// This function will be called when next page is pushed
  final Function? onPushNext;

  /// This function will be called when next page is poped
  final Function? onPopNext;

  @override
  State<RouteAwareWidget> createState() => _RouteAwareWidgetState();
}

class _RouteAwareWidgetState extends State<RouteAwareWidget> with RouteAware {
  @override
  Widget build(BuildContext context) {
    return widget.child;
  }

  @override
  void didPush() => _run(widget.onPush);

  @override
  void didPop() => _run(widget.onPop);

  @override
  void didPushNext() => _run(widget.onPushNext);

  @override
  void didPopNext() => _run(widget.onPopNext);

  void _run(Function? function) {
    if (function != null) function();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    routeObserver.subscribe(
        this, ModalRoute.of(context)! as PageRoute<dynamic>);
  }

  @override
  void dispose() {
    routeObserver.unsubscribe(this);

    super.dispose();
  }
}

Upvotes: 2

Related Questions