首页 文章

颤动:Hero过渡小部件动画同时出现?

提问于
浏览
1

所以,对于特定的动画案例,我在Flutter方面遇到了一些问题 .

基本上,我正在尝试做的是同时为路线更改运行英雄过渡运行和在相邻小部件上进行自定义动画 .

细分,我在我的root上有一个自定义的InheritedWidget,它从StatefulWidget父级提供一个app状态 . 嵌套在我的InheritedWidget中,我有一个WidgetsApp和一个自定义选项卡导航的相邻兄弟 . 树看起来像这样:

Root Widget (Stateful)
        |
        |__InheritedWidget
                   |
                   |__WidgetsApp (Handles routing)
                   |
                   |__Navigation Bar (Overlay)

当我在我的WidgetsApp上执行使用Hero过渡的路线更改时,我的问题出现了 . 在发生这种情况时,我还试图根据用户所在的视图显示或隐藏导航栏的动画 . 但是,由于我在我的应用程序状态中使用bool变量来通过动画显示或隐藏导航栏,因此SetState调用'覆盖'英雄转换,因为树在此过程中重建(就是我所知道的)思维) .

我最初的想法是,InheritedWidget会捕获应用程序状态更改并仅通过updateShouldNotify重建导航栏,但是这不是我所看到的所需的效果:(

那么 - 有没有人尝试过类似的东西,或者想知道如何优雅地处理它? :)

1 回答

  • 1

    我已经做了类似的事情,但遗憾的是我的代码还包含了许多其他的东西,这是相对复杂的事情,所以我必须把事情拆分成一个例子,这比现在我做的要多一些 . 我将解释我所做的一般概念 . 也许有更好的方法可以做到这一点 .

    您希望使用也扩展NavigatorObserver的State编写StatefulWidget(您可能可以使用无状态小部件,但我不这么认为) . 我个人将它放在树中的导航器上方(即它在其构建功能中构建导航器),但你很可能也在导航器旁边 .

    从NavigatorObserver覆盖didPush,didRemove,didPop等方法 . 在每个中,调用一个setState并保存动画和其他参数,如下所示:

    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;
            });
          }
        });
      }
    
      ....
    }
    

    在你的构建函数中,你需要根据它是否存在来检查_animation和animate,以及你可能想要设置的任何其他参数(即标记是否要设置动画,以及向前或向后是否有用 - 我相信'pop'动画已经从0开始并且与推进动画一样变为1,但我可能是错的) . 然后,您可以将此动画连接到想要为导航栏设置动画,可能使用AnimatedBuilder或直接连接动画或其他内容 . 如果有关于这一切是如何运作的任何具体问题,请发表评论,我会添加一些评论等 .

    希望有帮助=)

    编辑:完整的代码示例 . 为了记录,我不建议这个代码是那么好,或者这是你应该做的事情 . 但这是一种解决问题的方法 . 在真正的应用程序中使用它之前,值得测试它,并可能添加一些断言来检查状态等 .

    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();
      }
    }
    

相关问题