【问题标题】:Flutter Transition ExitFlutter 过渡出口
【发布时间】:2019-03-16 15:42:40
【问题描述】:

在Android API上我们可以使用

overridePendingTransition(int enterAnim, int exitAnim) 

定义进入和退出过渡。

如何在 Flutter 中实现?

我已经实现了这段代码

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

  );
}

但它只定义了输入转换。如何定义 de exit 过渡?

更新

想象一下,当我执行时,我有两个屏幕(Screen1 和 Screen2)

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

我想将动画应用到 Screen1 和 Screen2 而不仅仅是 Screen2

example

【问题讨论】:

    标签: flutter flutter-animation


    【解决方案1】:

    实现这一点的正确方法是使用在 PageRouteBuilder 对象的 transitionBuilder 中给出的 secondAnimation 参数。

    这里你可以在flutter sdk的flutter/lib/src/widgets/routes.dart文件中阅读更多关于secondaryAnimation参数的信息:

    ///
    /// 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,
    ///       ),
    ///     );
    ///   }
    /// ```
    

    这是一个使用 secondaryAnimation 参数的工作示例:

    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"))),
        );
      }
    }
    

    结果:

    【讨论】:

    • 能否请您添加第三条额外路线,并显示您是否仍然可以重现辅助动画....当我推送命名路线时,它的偏移量是 (-1, 0) 而不是 (0, 0)
    【解决方案2】:

    截图(Null Safe):


    我使用了不同的方式,但diegodeveloper提供的类似逻辑

    完整代码:

    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),
          ],
        );
      }
    }
    

    【讨论】:

    • 似乎使用这种方法或@diegodeveloper 方法,小部件在转换开始时调用 initState() 。使用 MaterialPageRoute 它不会发生。
    • 我已经用 MaterialPageRoute 尝试过这段代码,但是 initState() 还是被调用了......它会是什么?
    • 你是指第二页的initState() 吗?
    • 我的意思是 oldWidget 的 initState
    • @CopsOnRoad 如何在列表项上执行此动画?你能指导我吗?
    【解决方案3】:

    好问题,PageRouteBuilder 默认使用AnimationController 来处理动画转换,因此,当您关闭视图时,它只需从animationController 调用“reverse”方法,您将看到您正在使用的相同动画但反过来。

    如果您想在关闭视图时更改动画,您可以检查当前动画的状态并与AnimationStatus.reverse进行比较

    这是您的代码,当它反向时带有Fade 动画。

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

    解决方法

        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)
                        ],
                      );
                    });
        }
    

    用法:

     Navigator.of(context)
                  .push(SlideLeftRoute(enterWidget: Page2(), oldWidget: this));
    

    【讨论】:

    • 你是不是想说,如果我们正在运行 FadeTransition 来切换到一个新的路由,那么之前的路由也会运行这个转换,但是顺序相反?
    • @CopsOnRoad ,您可以尝试删除 slidetransition 和 animation.status 条件
    • 其实你的代码看起来不错,虽然我还没试过。正如您在第一段中所写的那样,先前的路线(屏幕)将自动以相反的顺序运行转换(默认情况下)。但是当我这样做时,旧路线仍然存在,只有新路线变得活跃。
    • @CopsOnRoad 完全正确.. 我想说的是,当我推动新屏幕时,前一个屏幕不会做任何动画。只有新屏幕有过渡。
    • 您可以在 SlideLeftRoute 中添加另一个参数并传递旧的孩子 :)
    【解决方案4】:

    还有另一种方法可以做到这一点。 initState() 在 oldWidget 中被调用的问题将不再存在。

    void main() => runApp(MaterialApp(theme: ThemeData.dark(), home: HomePage()));
    
    class HomePage extends StatefulWidget {
      @override
      _HomePageState createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("Page 1")),
          body: RaisedButton(
            child: Text("Next"),
            onPressed: () {
              Navigator.push(
                context,
                PageRouteBuilder(
                  pageBuilder: (c, a1, a2) => Page2(),
                  transitionsBuilder: (context, anim1, anim2, child) {
                    return SlideTransition(
                      position: Tween<Offset>(end: Offset(0, 0), begin: Offset(1, 0)).animate(anim1),
                      child: Page2(),
                    );
                  },
                ),
              );
            },
          ),
        );
      }
    }
    
    class Page2 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("Page 2")),
          body: RaisedButton(
            child: Text("Back"),
            onPressed: () => Navigator.pop(context),
          ),
        );
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-17
      • 2022-11-03
      • 2020-01-06
      • 2021-06-22
      • 1970-01-01
      • 2013-11-06
      • 1970-01-01
      • 2022-01-15
      相关资源
      最近更新 更多