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
);
},
),
)
],
),
);
}
);
}
我完全被它困住了。我想创建一个自定义页面路由,使 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
);
},
),
)
],
),
);
}
);
}