Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disabling Flutter Hero reverse animation

Given 2 routes, e.g. parent and a child and a Hero(..) widget with the same tag. When the user is on the "parent" screen and opens a "child" - the Hero widget is animated. When it goes back (via Navigator.pop) it's also animated.

I'm looking for a way to disable that animation when going back (from child to parent via Navigator.pop).

Is there a kind of handler which will be called on a widget before it's going to be "animated away" ? Then I probably could change Hero tag and problem solved.

Or, when creating a "builder" for a route in parent widget, I could probably remember a reference to a target widget and before calling Navigator.pop notify it about "you are gonna be animated out". That would also require making that widget stateful (I haven't found a way to force rebuild a stateless widget).

Is there an easier way of implementing this?

like image 534
Alexander Taran Avatar asked Oct 31 '18 17:10

Alexander Taran


2 Answers

While there currently isn’t a built-in way to disable Hero animations in any particular direction, though CLucera’s use of FadeTransition with HeroFlightDirection is one creative way, the most direct approach is to break the tag association between the two Hero’s:

When you go from the 2nd Hero back to the 1st Hero, just temporarily change the 1st Hero’s tag to something else, then the Hero won’t animate back. A simplified example:

class _MyHomePageState extends State<MyHomePage> {

  String tag1, tag2;
  String sharedTag = 'test';
  String breakTag = 'notTest';

  @override
  void initState() {
    super.initState();
    tag1 = sharedTag;
    tag2 = sharedTag;
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Hero(
            tag: tag1,
            child: RaisedButton(
              child: Text("hi"),
              onPressed: () {
                // restore the tag
                if (tag1 != sharedTag) {
                  setState(() {
                    tag1 = sharedTag;
                  });
                }

                // second route
                Navigator.of(context).push(
                    MaterialPageRoute<void>(
                        builder: (BuildContext context) {
                          return Scaffold(
                            appBar: AppBar(
                              title: Text(widget.title),
                            ),
                            body: Container(
                                alignment: Alignment.topLeft,
                                child: Hero(
                                  tag: tag2,
                                  child: RaisedButton(
                                    child: Text('hello'),
                                    onPressed: () {
                                      // change the tag to disable the reverse anim
                                      setState(() {
                                        tag1 = breakTag;
                                      });

                                      Navigator.of(context).pop();
                                    },
                                  ),
                                )
                            ),
                          );
                        }
                    )
                );
              },
            )
        ),
      ),
    );
  }
}

But if you want to directly modify the animation, then playing around inside the flightShuttleBuilder is the way to do it like CLucera did. You can also check out medium/mastering-hero-animations-in-flutter to further explore that area.

like image 145
TWL Avatar answered Oct 21 '22 21:10

TWL


The only approach that I can come up at the moment is to "Animate" the popping Hero in a way that seems not animated, let's check this code:

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Hero(
          flightShuttleBuilder: (context, anim, direction, fromContext, toContext) {
            final Hero toHero = toContext.widget;
            if (direction == HeroFlightDirection.pop) {
              return FadeTransition(
                opacity: AlwaysStoppedAnimation(0),
                child: toHero.child,
              );
            } else {
              return toHero.child;
            }
          },
          child: FlatButton(
            child: Text("prev 1"),
            onPressed: () {
              Navigator.of(context).pop();
            },
          ),
          tag: "test",
        ));
  }
}

in your SecondRoute (the one that should pop) you have to supply a flightShuttleBuilder parameter to your Hero then you can check the direction and if it is popping, just hide the Widget with an AlwaysStoppedAnimation fade transition

the result is something like this: enter image description here

I hope that this is something like the expected result, of course, you can completely change the transition inside the flightShuttleBuilder to change the effect! it's up to you :)

like image 27
CLucera Avatar answered Oct 21 '22 23:10

CLucera