Flutter: анимация двух маршрутов с внутренней анимацией

Я полностью застрял в этом. Я хочу создать собственный маршрут страницы, который будет анимировать как страницы 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)
);

И это сработало. Анимация страницы выполнялась вместе с переходом страницы. Но мне не удалось перевернуть контроллер, когда мне нужно было открыть маршрут. Я имею в виду, что я мог бы, конечно, например, в методе didWidgetUpdate, но это не имело никакого эффекта, потому что виджет (который в этом примере называется nextPage) потерял свой контекст, а контроллер анимации анимировал другую копию виджета, который не отображался до тех пор, пока поп-переход закончился. Во время выполнения всплывающего перехода отображалась старая копия "nextPage"

Вторая идея заключалась в том, чтобы использовать GlobalKey для сохранения состояния виджета, который также потерпел неудачу с ошибкой использования двух повторяющихся глобальных ключей.

Идеальным вариантом было бы что-то подобное в моем 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
                    ),
                  );
                },
              ),
            )
          ],
        ),
      );
    }
  );
}

Мне нужно начать перемещать синий квадрат, когда начинается переход страницы, и вернуть анимацию этого квадрата в всплывающем окне маршрута.

1 ответ

Решение

Я только что пришел к решению. Я могу получить доступ к анимации через

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
                  );
                },
              ),
            )
          ],
        ),
      );
    }
  );
}
Другие вопросы по тегам