【问题标题】:Flutter: Hero transition + widget animation at the same time?Flutter:同时进行英雄过渡 + 小部件动画?
【发布时间】:2018-08-31 03:02:24
【问题描述】:

所以,关于特定动画案例,我对 Flutter 有点问题。

基本上,我想要做的是同时为路线更改运行英雄转换和相邻小部件上的自定义动画。

分解后,我的根目录中有一个自定义 InheritedWidget,它从 StatefulWidget 父级提供应用程序状态。嵌套在我的 InheritedWidget 中,我有一个 WidgetsApp 和一个用于自定义选项卡导航的相邻兄弟。树看起来像这样:

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

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

我最初的想法是 InheritedWidget 会捕捉应用程序状态的变化,并且只通过 updateShouldNotify 重建导航栏,但这并不是我所期望的效果 :(

那么 - 有没有人尝试过类似的事情,或者知道如何优雅地处理这个问题? :)

【问题讨论】:

    标签: animation dart flutter


    【解决方案1】:

    我做过类似的事情,但不幸的是,我的代码还包含一堆其他的东西,这相对来说比较复杂,所以我不得不把事情分开来做一个比我能做的多一点的例子现在。我将解释我所做的一般概念。也可能有更好的方法来做到这一点。

    您想编写一个 StatefulWidget,其 State 还扩展了 NavigatorObserver(您可能可以使用无状态小部件,但我不这么认为)。我个人将它放在树中导航器的上方(即它在其构建函数中构建导航器),但您很可能也将它放在导航器的“旁边”。

    从 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 和基于它是否存在的动画,以及您可能想要设置的任何其他参数(即是否动画的标志,以及是前进还是后退)有帮助 - 我相信“pop”动画已经从 0 开始,然后到 1 与 push 一样,但我可能是错的)。 然后,您可以将此动画连接到您想要为导航栏设置动画的任何地方,可能使用 AnimatedBuilder 或直接连接动画,或其他方式。如果对这一切如何运作有任何具体问题,请发表评论,我会添加一些 cmets 等。

    希望对您有所帮助 =)

    编辑:带有完整的代码示例。作为记录,我不建议这段代码那么好,或者这是你应该做的事情。但这是解决问题的一种方式。在实际应用中使用它之前,值得对其进行测试,并可能添加一些断言来检查状态等。

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

    【讨论】:

    • 非常感谢您花时间回答! :) 我了解您的概念的基本概念,但我还是很陌生,所以如果您确实有时间发布一些示例代码,我很乐意看到它:)
    • 我看看能不能找到时间!
    • @JacobHK 我添加了一个代码示例。希望它在做什么已经足够清楚了。代码肯定可以进行一些清理,但这应该可以理解 =)。哦,为了记录,这可能不是 Flutter 人建议这样做的方式。
    • 非常感谢您的详细回复!我一定会更深入地研究您的解决方案的基础并进行测试! :)
    猜你喜欢
    • 1970-01-01
    • 2020-05-15
    • 2021-06-23
    • 1970-01-01
    • 2020-04-23
    • 2018-11-01
    • 2021-10-16
    • 1970-01-01
    • 2020-11-17
    相关资源
    最近更新 更多