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