Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter Transition Exit

On Android API we can use

overridePendingTransition(int enterAnim, int exitAnim) 

to define the enter and exit transitions.

How to do it in Flutter?

I have implemented this code

class SlideLeftRoute extends PageRouteBuilder {
  final Widget enterWidget;
  SlideLeftRoute({this.enterWidget})
      : super(
      pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
        return enterWidget;
      },
      transitionsBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
        return SlideTransition(
          position: new Tween<Offset>(
            begin: const Offset(1.0, 0.0),
            end: Offset.zero,
          ).animate(animation),
          child: child
        );
      },

  );
}

but it only defines the enter transition. How can i define de exit transition?

UPDATE

Imagine that i have two screens (Screen1 and Screen2), when i execute

 Navigator.push(
        context, SlideLeftRoute(enterWidget: Screen2()));

i'd like to apply an animation to both Screen1 and Screen2 and not only to Screen2

example

like image 859
Guilherme Sant'Ana Avatar asked Oct 11 '18 14:10

Guilherme Sant'Ana


People also ask

How do I turn off animation in Flutter?

As a workaround, you can use the transition-duration property of PageRouteBuilder Widget. Navigator. pushReplacement( context, PageRouteBuilder( pageBuilder: (context, animation1, animation2) => Page1(), transitionDuration: Duration(seconds: 0), ), );

What is a Pageroute?

A modal route that replaces the entire screen.

How do I navigate to a page without animation Flutter?

My solution is to define the route with isInitialRoute:true . This prevents Flutter from showing an animation when the route is pushed. The simplest way to achieve a simple result: navigate without animations.


3 Answers

Good question , the PageRouteBuilder use an AnimationController by default to handle the animation transition so, when you dismiss your view, it just call 'reverse' method from the animationController and you will see the same animation you are using but in reverse.

In case you want to change the animation when you dismiss your view you can do it checking the status of the current animation and compare with AnimationStatus.reverse

This is your code with a Fade animation when it's in reverse.

  class SlideLeftRoute extends PageRouteBuilder {
    final Widget enterWidget;
    SlideLeftRoute({this.enterWidget})
        : super(
            pageBuilder: (BuildContext context, Animation<double> animation,
                Animation<double> secondaryAnimation) {
              return enterWidget;
            },
            transitionsBuilder: (BuildContext context,
                Animation<double> animation,
                Animation<double> secondaryAnimation,
                Widget child) {
              if (animation.status == AnimationStatus.reverse) {
                //do your dismiss animation here
                return FadeTransition(
                  opacity: animation,
                  child: child,
                );
              } else {
                return SlideTransition(
                    position: new Tween<Offset>(
                      begin: const Offset(1.0, 0.0),
                      end: Offset.zero,
                    ).animate(animation),
                    child: child);
              }
            },
          );
  }

WORKAROUND

    class SlideLeftRoute extends PageRouteBuilder {
      final Widget enterWidget;
      final Widget oldWidget;

      SlideLeftRoute({this.enterWidget, this.oldWidget})
          : super(
                transitionDuration: Duration(milliseconds: 600),
                pageBuilder: (BuildContext context, Animation<double> animation,
                    Animation<double> secondaryAnimation) {
                  return enterWidget;
                },
                transitionsBuilder: (BuildContext context,
                    Animation<double> animation,
                    Animation<double> secondaryAnimation,
                    Widget child) {
                  return Stack(
                    children: <Widget>[
                      SlideTransition(
                          position: new Tween<Offset>(
                            begin: const Offset(0.0, 0.0),
                            end: const Offset(-1.0, 0.0),
                          ).animate(animation),
                          child: oldWidget),
                      SlideTransition(
                          position: new Tween<Offset>(
                            begin: const Offset(1.0, 0.0),
                            end: Offset.zero,
                          ).animate(animation),
                          child: enterWidget)
                    ],
                  );
                });
    }

Usage:

 Navigator.of(context)
              .push(SlideLeftRoute(enterWidget: Page2(), oldWidget: this));
like image 67
diegoveloper Avatar answered Nov 08 '22 16:11

diegoveloper


The correct way of achieving this is to use the secondaryAnimation parameter that is given in the transitionBuilder of a PageRouteBuilder object.

Here you can read more about the secondaryAnimation parameter in the documentation in the flutter/lib/src/widgets/routes.dart file in the flutter sdk:

///
/// When the [Navigator] pushes a route on the top of its stack, the
/// [secondaryAnimation] can be used to define how the route that was on
/// the top of the stack leaves the screen. Similarly when the topmost route
/// is popped, the secondaryAnimation can be used to define how the route
/// below it reappears on the screen. When the Navigator pushes a new route
/// on the top of its stack, the old topmost route's secondaryAnimation
/// runs from 0.0 to 1.0. When the Navigator pops the topmost route, the
/// secondaryAnimation for the route below it runs from 1.0 to 0.0.
///
/// The example below adds a transition that's driven by the
/// [secondaryAnimation]. When this route disappears because a new route has
/// been pushed on top of it, it translates in the opposite direction of
/// the new route. Likewise when the route is exposed because the topmost
/// route has been popped off.
///
/// ```dart
///   transitionsBuilder: (
///       BuildContext context,
///       Animation<double> animation,
///       Animation<double> secondaryAnimation,
///       Widget child,
///   ) {
///     return SlideTransition(
///       position: AlignmentTween(
///         begin: const Offset(0.0, 1.0),
///         end: Offset.zero,
///       ).animate(animation),
///       child: SlideTransition(
///         position: TweenOffset(
///           begin: Offset.zero,
///           end: const Offset(0.0, 1.0),
///         ).animate(secondaryAnimation),
///         child: child,
///       ),
///     );
///   }
/// ```

This is a working example using the secondaryAnimation parameter:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      onGenerateRoute: (RouteSettings settings) {
        if (settings.name == '/') {
          return PageRouteBuilder<dynamic>(
            pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => Page1(),
            transitionsBuilder: (
              BuildContext context,
              Animation<double> animation,
              Animation<double> secondaryAnimation,
              Widget child,
            ) {
              final Tween<Offset> offsetTween = Tween<Offset>(begin: Offset(0.0, 0.0), end: Offset(-1.0, 0.0));
              final Animation<Offset> slideOutLeftAnimation = offsetTween.animate(secondaryAnimation);
              return SlideTransition(position: slideOutLeftAnimation, child: child);
            },
          );
        } else {
          // handle other routes here
          return null;
        }
      },
    );
  }
}

class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Page 1")),
      backgroundColor: Colors.blue,
      body: Center(
        child: RaisedButton(
          onPressed: () => Navigator.push(
            context,
            PageRouteBuilder<dynamic>(
              pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => Page2(),
              transitionsBuilder: (
                BuildContext context,
                Animation<double> animation,
                Animation<double> secondaryAnimation,
                Widget child,
              ) {
                final Tween<Offset> offsetTween = Tween<Offset>(begin: Offset(1.0, 0.0), end: Offset(0.0, 0.0));
                final Animation<Offset> slideInFromTheRightAnimation = offsetTween.animate(animation);
                return SlideTransition(position: slideInFromTheRightAnimation, child: child);
              },
            ),
          ),
          child: Text("Go to Page 2"),
        ),
      ),
    );
  }
}

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Page 2"), backgroundColor: Colors.green),
      backgroundColor: Colors.green,
      body: Center(child: RaisedButton(onPressed: () => Navigator.pop(context), child: Text("Back to Page 1"))),
    );
  }
}

Result:

enter image description here

like image 42
janosch Avatar answered Nov 08 '22 16:11

janosch


Screenshot (Null Safe):

enter image description here


I used a different way, but similar logic provided by diegodeveloper

Full code:

void main() => runApp(MaterialApp(home: Page1()));

class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey,
      appBar: AppBar(title: Text('Page 1')),
      body: Center(
        child: ElevatedButton(
          onPressed: () => Navigator.push(
            context,
            MyCustomPageRoute(
              parent: this,
              builder: (context) => Page2(),
            ),
          ),
          child: Text('2nd Page'),
        ),
      ),
    );
  }
}

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.blueGrey,
      appBar: AppBar(title: Text('Page 2')),
      body: Center(
        child: ElevatedButton(
          onPressed: () => Navigator.pop(context),
          child: Text('Back'),
        ),
      ),
    );
  }
}

class MyCustomPageRoute<T> extends MaterialPageRoute<T> {
  final Widget parent;

  MyCustomPageRoute({
    required this.parent,
    required WidgetBuilder builder,
    RouteSettings? settings,
  }) : super(builder: builder, settings: settings);

  @override
  Widget buildTransitions(_, Animation<double> animation, __, Widget child) {
    var anim1 = Tween<Offset>(begin: Offset.zero, end: Offset(-1.0, 0.0)).animate(animation);
    var anim2 = Tween<Offset>(begin: Offset(1.0, 0.0), end: Offset.zero).animate(animation);
    return Stack(
      children: <Widget>[
        SlideTransition(position: anim1, child: parent),
        SlideTransition(position: anim2, child: child),
      ],
    );
  }
}
like image 8
CopsOnRoad Avatar answered Nov 08 '22 15:11

CopsOnRoad