Flutter: Hero transition + widget animation at the same time? Flutter: Hero transition + widget animation at the same time? dart dart

Flutter: Hero transition + widget animation at the same time?


I have done something similar, but unfortunately my code also contains a bunch of other stuff & this is relatively convoluted to do, so I'd have to split things out to make an example which is a bit more than I can do right now. I'll explain the general concept of what I did though. There may also be better ways of doing this.

You want to write a StatefulWidget with a State that also extends NavigatorObserver (you may be able to use a stateless widget but I don't think so). I personally put this above the navigator in the tree (i.e. it builds the navigator in its' build function), but you could most likely also have it 'beside' the navigator.

Override the didPush, didRemove, didPop etc methods from NavigatorObserver. Within each of these, call a setState and save the animation & other paramters, something like this:

class NavigationFaderState extends State<NavigationFader> with NavigatorObserver {  Animation _animation;  // whatever else you need, maybe starting/finishing opacity or position etc.  @override  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {    setState(() {      _animation = route.animation;    }    route.animation.addStatusListener((status) {      if (status = AnimationStatus.completed) {        setState(() {          _animation = null;        });      }    });  }  ....}

In your build function you'll want to check the _animation and animate based on whether it exists, and any other parameters you might want to set (i.e. a flag whether to animate, and whether the is going forward or backwards could be helpful - I believe the 'pop' animation have have started at 0 and gone to 1 the same as the push one but I could be wrong).You can then hook up this animation to however you want to animate your navigation bar, probably using an AnimatedBuilder or hooking up the animation directly, or something. If there are any specific questions about how this all works, comment and I'll add some comments etc.

Hope that helps =)

EDIT: With full code example. For the record, I don't propose that this code is all that good, or that this is something you should do. But it is a way of solving the problem. Before using it in a real app, it would be worth testing it and probably adding some assertions to check for states etc.

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());class MyApp extends StatelessWidget {  PushListener listener = new PushListener();  @override  Widget build(BuildContext context) {    return new WidgetsApp(      locale: new Locale("en"),      navigatorObservers: [listener],      builder: (context, child) {        // this is here rather than outside the WidgetsApp so that it        // gets access to directionality, text styles, etc        return new Scaffold(          body: child,          bottomNavigationBar:              new ColorChangingNavigationBar(key: listener.navBarKey),        );      },      onGenerateRoute: (settings) {        switch (settings.name) {          case '/':            return new MaterialPageRoute(              settings: settings,              builder: (context) => Column(                    children: <Widget>[                      new Text(                          "I have a green nav bar when you open me and blue when you come back"),                      new RaisedButton(                        onPressed: () {                          Navigator.pushNamed(context, "/red");                        },                        child: new Text("Next"),                      ),                    ],                  ),            );          case '/red':            return new MaterialPageRoute(              settings: settings,              builder: (context) => Column(                    children: <Widget>[                      new Text("I have a red nav bar"),                      new RaisedButton(                        onPressed: () {                          Navigator.pop(context);                        },                      )                    ],                  ),            );        }      },      color: Colors.blue,    );  }}class PushListener extends NavigatorObserver {  GlobalKey<ColorChangingNavigationBarState> navBarKey = new GlobalKey();  @override  void didPop(Route route, Route previousRoute) {    if (route is ModalRoute && navBarKey.currentState != null) {      var name = route.settings.name;      var color = name == "/" ? Colors.red.shade500 : Colors.blue.shade500;      var animation = new ReverseAnimation(route.animation);      print("Popping & changing color to: ${name == "/" ? "red" : "blue"}");      navBarKey.currentState.setAnimating(animation, color);    }  }  @override  void didPush(Route route, Route previousRoute) {    if (route is ModalRoute && navBarKey.currentState != null) {      var name = route.settings.name;      var color = name == "/" ? Colors.blue.shade500 : Colors.red.shade500;      print("Pushing & changing color to: ${name == "/" ? "red" : "blue"}");      var animation = route.animation;      navBarKey.currentState.setAnimating(animation, color);    }  }  @override  void didRemove(Route route, Route previousRoute) {    // probably don't need  }  @override  void didStartUserGesture() {    // might want to do if gestures are supported with whichever type of    // route you're using.  }  @override  void didStopUserGesture() {    // if you implement didStartUserGesture  }}class ColorChangingNavigationBar extends StatefulWidget {  final Color startColor;  ColorChangingNavigationBar(      {Key key, this.startColor = const Color.fromRGBO(0, 255, 0, 1.0)})      : super(key: key);  @override  State<StatefulWidget> createState() => new ColorChangingNavigationBarState();}class _ColorAnimationInfo {  final Animation animation;  final Tween<Color> colorTween;  final AnimationStatusListener statusListener;  _ColorAnimationInfo(this.animation, this.colorTween, this.statusListener);}class ColorChangingNavigationBarState    extends State<ColorChangingNavigationBar> {  @override  void initState() {    _toColor = widget.startColor;    super.initState();  }  Color _toColor;  _ColorAnimationInfo _colorAnimationInfo;  void setAnimating(Animation animation, Color to) {    var fromColor;    if (_colorAnimationInfo != null) {      fromColor = _colorAnimationInfo.colorTween          .lerp(_colorAnimationInfo.animation.value);      _colorAnimationInfo.animation          .removeStatusListener(_colorAnimationInfo.statusListener);    } else {      fromColor = _toColor;    }    var statusListener = (state) {      if (state == AnimationStatus.completed ||          state == AnimationStatus.dismissed) {        setState(() {          _colorAnimationInfo = null;        });      }    };    animation.addStatusListener(statusListener);    setState(() {      _toColor = to;      Tween<Color> colorTween = new ColorTween(begin: fromColor, end: to);      _colorAnimationInfo =          new _ColorAnimationInfo(animation, colorTween, statusListener);    });  }  @override  Widget build(BuildContext context) {    if (_colorAnimationInfo != null) {      return new AnimatedBuilder(          animation: _colorAnimationInfo.animation,          builder: (context, child) {            return new Container(              color: _colorAnimationInfo.colorTween                  .lerp(_colorAnimationInfo.animation.value),              height: 30.0,            );          });    } else {      return new Container(        color: _toColor,        height: 30.0,      );    }  }  @override  void dispose() {    if (_colorAnimationInfo != null) {      _colorAnimationInfo.animation.removeStatusListener(_colorAnimationInfo.statusListener);    }    _colorAnimationInfo = null;    super.dispose();  }}