Flutter 过渡出口
Posted
技术标签:
【中文标题】Flutter 过渡出口【英文标题】:Flutter Transition Exit 【发布时间】: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
【问题讨论】:
【参考方案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),
),
);
【讨论】:
以上是关于Flutter 过渡出口的主要内容,如果未能解决你的问题,请参考以下文章