Flutter 使用内部动画为两条路线设置动画

Flutter animating two routes with inner animations

我完全被它困住了。我想创建一个自定义页面路由,使 IN 和 OUT 页​​面都具有动画效果。路线本身的动画是一项简单的任务,我是这样做的:

    import 'package:flutter/material.dart';

    class FromMenuRoute extends PageRouteBuilder {

      final Widget nextPage;
      final Widget prevPage;

      FromMenuRoute({this.prevPage, this.nextPage}) : super(
        transitionDuration: Duration(milliseconds: 500),
        pageBuilder: (
          BuildContext context,
          Animation<double> animation,
          Animation<double> secondaryAnimation,
        ) {
          return nextPage;
        },
        maintainState: false,
        transitionsBuilder: (
          BuildContext context,
          Animation<double> animation,
          Animation<double> secondaryAnimation,
          Widget child,
        ) {
          var colorTheme = CustomThemeData.of(context).colorTheme;
          return Material(
            child: Stack(
              overflow: Overflow.visible,
              children: <Widget>[
                SlideTransition(
                  position: Tween<Offset>(
                    begin: const Offset(0.0, 0.0),
                    end: const Offset(-0.3, 0.0),
                  ).animate(animation),
                  child: prevPage,
                ),
                SlideTransition(
                  position: Tween<Offset>(
                    begin: const Offset(1.0, 0.0),
                    end: Offset.zero,
                  ).animate(animation),
                  child: AnimatedBuilder(
                    animation: animation,
                    builder: (c, w) {
                      return Material(
                        shadowColor: colorTheme.textColor,
                        elevation: 30.0 * animation.value,
                        child: nextPage
                      );
                    },
                  ),
                )
              ],
            ),
          );
        }
      );
    }

但问题是我还想 运行 在这些页面中添加一些动画,而不仅仅是为页面小部件本身设置动画。我不知道该怎么做。

我正在使用这样的路线

Navigator.of(context).push(
    FromMenuRoute(prevPage: widget, nextPage: nextPageWidget)
);

我的第一个想法是在推送路由之前在 OUT 页​​面启动一个动画控制器:

_animationController.forward();
Navigator.of(context).push(
    FromMenuRoute(prevPage: widget, nextPage: nextPageWidget)
);

它奏效了。页面动画 运行 与页面 t运行 同步。但是当我需要弹出路由时,我无法设法反转控制器。 我的意思是,我当然可以,例如在 didWidgetUpdate 方法中,但它没有效果,因为小部件(在本例中称为 nextPage)失去了它的上下文并且动画控制器动画了小部件的另一个副本,该副本直到 pop t运行sition 结束才显示. 虽然 pop t运行sition 运行 它显示了 "nextPage"

的旧副本

第二个想法是使用 GlobalKey 来保持小部件的状态,该小部件也因错误而失败 使用 2 个重复的全局键。

我的 FromMenuRoute 中的理想选择是这样的

SlideTransition(
   position: Tween<Offset>(
    begin: const Offset(0.0, 0.0),
    end: const Offset(-0.3, 0.0),
  ).animate(animation),
  child: prevPage.copyWith(animation: animation), // but I understand this can't be done
),

所以我 运行 没有好主意。我不想以某种非常棘手的方式做到这一点。也许有一些我不知道的更好的解决方案。如果你知道如何解决这个问题,请分享它


这是我想要实现的详细示例

import 'package:flutter/material.dart';

class Page1 extends StatefulWidget {
  @override
  _Page1State createState() => _Page1State();
}

class _Page1State extends State<Page1> with SingleTickerProviderStateMixin {


  AnimationController _animationController;

  @override
  void initState() {
    _animationController = AnimationController(
      vsync: this,
      lowerBound: 0.0,
      upperBound: 1.0,
      duration: Duration(milliseconds: 3000)
    );
    super.initState();
  }
  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red,
      width: double.infinity,
      height: double.infinity,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Container(
            width: double.infinity,
            height: 60.0,
            color: Colors.amber,
            child: Stack(
              children: <Widget>[
                SlideTransition(
                  position: Tween<Offset>(
                    begin: const Offset(0.0, 0.0),
                    end: Offset(3.0, 0.0),
                  ).animate(_animationController),
                  child: Container(
                    height: 60.0,
                    width: 60.0,
                    color: Colors.blue,
                  ),
                ),
              ],
            ),
          ),
          SizedBox(height: 100.0,),
          RaisedButton(
            child: Text('Add new route'),
            onPressed: () {
              /// calling here doe not affect animation because
              /// a new widget is created on top of this
              /// but is I call it from initState() I won't have access to the 
              /// controller later, when I need to revert the animation
              _animationController.forward();
              Navigator.of(context).push(
                FromMenuRoute(prevPage: widget, nextPage: Page2())
              );
            },
          ),
        ],
      ),
    );
  }
}


class Page2 extends StatefulWidget {
  @override
  _Page2State createState() => _Page2State();
}

class _Page2State extends State<Page2> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      width: double.infinity,
      height: double.infinity,
    );
  }
}


class FromMenuRoute extends PageRouteBuilder {

  final Widget nextPage;
  final Widget prevPage;

  FromMenuRoute({this.prevPage, this.nextPage}) : super(
    transitionDuration: Duration(milliseconds: 3000),
    pageBuilder: (
      BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
    ) {
      return nextPage;
    },
    maintainState: true,
    transitionsBuilder: (
      BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
      Widget child,
    ) {
      return Material(
        child: Stack(
          overflow: Overflow.visible,
          children: <Widget>[
            SlideTransition(
              position: Tween<Offset>(
                begin: const Offset(0.0, 0.0),
                end: const Offset(-0.3, 0.0),
              ).animate(animation),
              child: prevPage,
            ),

            SlideTransition(
              position: Tween<Offset>(
                begin: const Offset(1.0, 0.0),
                end: Offset.zero,
              ).animate(animation),
              child: AnimatedBuilder(
                animation: animation,
                builder: (c, w) {
                  return Opacity(
                    opacity: .8,
                    child: Material(
                      shadowColor: Colors.black,
                      elevation: 30.0 * animation.value,
                      child: nextPage
                    ),
                  );
                },
              ),
            )
          ],
        ),
      );
    }
  );
}

我需要在页面 t运行sition 启动时开始移动一个蓝色方块,并在 pop

路径上还原该方块的动画

我刚刚找到了解决方案。我可以通过

访问动画
ModalRoute.of(context).animation;

这是工作示例

import 'package:flutter/material.dart';

class Page1 extends StatefulWidget {
  @override
  _Page1State createState() => _Page1State();
}

class _Page1State extends State<Page1> with SingleTickerProviderStateMixin {

  @override
  Widget build(BuildContext context) {
    var modalRoute = ModalRoute.of(context);

    return Container(
      color: Colors.red,
      width: double.infinity,
      height: double.infinity,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Container(
            width: double.infinity,
            height: 60.0,
            color: Colors.amber,
            child: Stack(
              children: <Widget>[
                AnimatedBuilder(
                  animation: modalRoute.animation,
                  builder: (c, w) {
                    var isCompleted = modalRoute.animation.status == AnimationStatus.completed;
                    var posX = 150.0 * modalRoute.animation.value;
                    if (isCompleted) {
                      posX = 0;
                    }
                    return Transform(
                      transform: Matrix4.translationValues(posX, 0, 0),
                      child: Container(
                        height: 60.0,
                        width: 60.0,
                        color: Colors.blue,
                      ),
                    );
                  },
                ),
              ],
            ),
          ),
          SizedBox(height: 100.0,),
          RaisedButton(
            child: Text('Add new route'),
            onPressed: () {
              Navigator.of(context).push(
                FromMenuRoute(prevPage: widget, nextPage: Page2())
              );
            },
          ),
        ],
      ),
    );
  }
}


class Page2 extends StatefulWidget {
  @override
  _Page2State createState() => _Page2State();
}

class _Page2State extends State<Page2> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      width: double.infinity,
      height: double.infinity,
    );
  }
}


class FromMenuRoute extends PageRouteBuilder {

  final Widget nextPage;
  final Widget prevPage;

  FromMenuRoute({this.prevPage, this.nextPage}) : super(
    transitionDuration: Duration(milliseconds: 3000),
    pageBuilder: (
      BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
    ) {
      return nextPage;
    },
    maintainState: true,
    transitionsBuilder: (
      BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
      Widget child,
    ) {
      return Material(
        child: Stack(
          overflow: Overflow.visible,
          children: <Widget>[
            SlideTransition(
              position: Tween<Offset>(
                begin: const Offset(0.0, 0.0),
                end: const Offset(-0.3, 0.0),
              ).animate(animation),
              child: prevPage,
            ),

            SlideTransition(
              position: Tween<Offset>(
                begin: const Offset(1.0, 0.0),
                end: Offset.zero,
              ).animate(animation),
              child: AnimatedBuilder(
                animation: animation,
                builder: (c, w) {
                  return Material(
                    shadowColor: Colors.black,
                    elevation: 30.0 * animation.value,
                    child: nextPage
                  );
                },
              ),
            )
          ],
        ),
      );
    }
  );
}