Flutter:英雄过渡+小部件动画同时出现? [英] Flutter: Hero transition + widget animation at the same time?

查看:72
本文介绍了Flutter:英雄过渡+小部件动画同时出现?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以,在特定的动画情况下,Flutter有点问题。



基本上,我想同时做



总而言之,我在根目录下有一个自定义的InheritedWidget,它被喂给了一个应用程序。



StatefulWidget父级的状态。嵌套在我的InheritedWidget中,我有一个WidgetsApp和一个用于自定义标签导航的相邻兄弟姐妹。树看起来像这样:

  Root Widget(有状态)
|
| __InheritedWidget
|
| __WidgetsApp(处理路由)
|
| __导航栏(覆盖图)

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



我最初的想法是InheritedWidget将捕获应用程序状态更改,仅通过updateShouldNotify重建导航栏,但是可惜这不是我的意思看到预期的效果:(



所以-有没有人尝试过类似的方法,或者对如何妥善处理有想法?:)

解决方案

我做过类似的事情,但不幸的是,我的代码还包含许多其他内容。这是相对复杂的事情,所以我不得不把事情分解成一个例子,这比我现在能做的要多。我将解释我所做的一般概念。也许还有更好的方法。



您想编写一个StatefulWidget,其State也扩展了NavigatorObserver(您可以使用无状态的widget,但我可以不要这样)。我个人将其放置在树中的导航器上方(即,它在其 build功能中构建了导航器),但是您很可能也将其置于导航器的旁边。



从NavigatorObserver重写didPush,didRemove,didPop等方法。在其中的每一个中,调用setState并保存动画&其他参数,如下所示:

 类NavigationFaderState扩展State< NavigationFader>与NavigatorObserver {

Animation _animation;
//您还需要其他什么,也许开始/完成不透明度或位置等。

@override
void didPush(Route< dynamic>路线,Route< dynamic> previousRoute){
setState((){
_animation = route.animation;
}
route.animation.addStatusListener((status){
if(status = AnimationStatus.completed) {
setState((){
_animation = null;
});
}
});
}

....
}

在构建函数中,您需要检查_animation并根据是否存在动画以及您可能要设置的任何其他参数进行动画处理(例如,标记是否要进行动画处理,以及动画的前进或后退标记都可能会有所帮助-我认为 pop动画已从0开始,变成了与推入相同的1,但是我可能错了。)
然后,您可以将这个动画连接起来,但是想要为导航栏设置动画,可能使用AnimatedBuilder或直接连接动画等。如果对此有任何疑问,请发表评论,我会添加一些评论,等等。



希望有帮助=)



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



import'package:flutter / material.dart' ;

  void main()=> runApp(new MyApp()); 

类MyApp扩展了StatelessWidget {
PushListener listener = new PushListener();

@override
Widget build(BuildContext context){
return new WidgetsApp(
locale:new Locale( en),
navigatorObservers:[侦听器],
构建器:(上下文,儿童){
//这是在这里,而不是在WidgetsApp之外,因此它
//可访问方向性,文本样式等
返回新的Scaffold(
body:child,
bottomNavigationBar:
new ColorChangingNavigationBar(key:listener.navBarKey),
);
},
onGenerateRoute :(设置){
开关(settings.name){
case'/':
返回新的MaterialPageRoute(
settings:设置,
builder:(上下文) =>列(
个孩子:< Widget> [
新文本(
打开时我有绿色的导航栏,回来时有蓝色的导航栏)),
新的RaisedButton(
onPressed:(){
Navigator.pushNamed(context, / red);
},
子级:new Text( Next),
),
],
),
);
case'/ red':
返回新的MaterialPageRoute(
settings:设置,
builder:(context)=> Column(
children:< Widget> [
new Text( I have a red nav bar),
new RaisedButton(
onPressed:(){
Navigator.pop(context);
} ,

],
),
);
}
},
color:Colors.blue,
);
}
}

类PushListener扩展了NavigatorObserver {
GlobalKey< ColorChangingNavigationBarState> navBarKey = new GlobalKey();

@override
void didPop(Route route,Route previousRoute){
if(route is ModalRoute&& navBarKey.currentState!= null){
var名称= route.settings.name;
var color = name == /吗? Colors.red.shade500:Colors.blue.shade500;
var animation = new ReverseAnimation(route.animation);
print(弹出并更改颜色为:$ {name == /? red: blue});

navBarKey.currentState.setAnimating(动画,颜色);
}
}

@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(将颜色推入并更改为:$ {name == /? red: blue});
var animation = route.animation;
navBarKey.currentState.setAnimating(动画,颜色);
}
}

@override
void didRemove(Route route,Route previousRoute){
//可能不需要
}

@override
void didStartUserGesture(){
//如果您使用的
类型的路线都支持手势,则可能要这样做。
}

@override
void didStopUserGesture(){
//如果实现didStartUserGesture
}
}

类ColorChangingNavigationBar扩展了StatefulWidget {
final Color startColor;

ColorChangingNavigationBar(
{Key key,this.startColor = const Color.fromRGBO(0,255,0,1.0)})
:super(key:key);

@override
State< StatefulWidget> createState()=>新的ColorChangingNavigationBarState();
}

class _ColorAnimationInfo {
最终的Animation动画;
最终Tween< Color> colorTween;
final AnimationStatusListener statusListener;

_ColorAnimationInfo(this.animation,this.colorTween,this.statusListener);
}

类ColorChangingNavigationBarState
扩展了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 =(状态){
if(状态== AnimationStatus.completed ||
state == AnimationStatus.dismissed){
setState((){
_colorAnimationInfo = null;
});
}
};

animation.addStatusListener(statusListener);

setState((){
_toColor = to;
Tween< Color> colorTween = new ColorTween(begin:fromColor,end:to);

_colorAnimationInfo =
新的_ColorAnimationInfo(动画,colorTween,statusListener);
});
}

@override
小部件构建(BuildContext上下文){
if(_colorAnimationInfo!= null){
返回新的AnimatedBuilder(
动画:_colorAnimationInfo.animation,
构建器:(上下文,子级){
返回新的容器(
颜色:_colorAnimationInfo.colorTween
.lerp(_colorAnimationInfo.animation.value),
高度:30.0,
);
});
}否则{
返回新容器(
颜色:_toColor,
高度:30.0,
);
}
}

@override
void dispose(){
if(_colorAnimationInfo!= null){
_colorAnimationInfo.animation.removeStatusListener (_colorAnimationInfo.statusListener);
}
_colorAnimationInfo = null;
super.dispose();
}
}


So, I'm having a bit of an issue with Flutter in regards to a specific animation case.

Basically, what I'm trying to do is simultaneously have both a hero transition run for a route change and a custom animation on an adjacent widget.

Broken down, I have a custom InheritedWidget at my root which is fed an app state from a StatefulWidget parent. Nested within my InheritedWidget, I have a WidgetsApp and an adjacent sibling for a custom tab navigation. The tree looks something like this:

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

My issue arises when I on my WidgetsApp perform a route change which uses a Hero transition. While this is happening, I'm trying to also animate the Navigation Bar to either be shown or hidden depending on what view the user is on. But, since I'm using a bool variable on my app state to either show or hide the Navigation Bar via an animation, the SetState call there 'overwrites' the hero transition since the tree is rebuilt in the process (is what I'm thinking).

My initial thought was that the InheritedWidget would catch the app state change and only rebuild the Navigation Bar via updateShouldNotify, but alas this isn't what I'm seeing as the desired effect :(

So - has anyone tried anything similar, or have an idea as to how this could be handled gracefully? :)

解决方案

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

这篇关于Flutter:英雄过渡+小部件动画同时出现?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆