我们在第二章“路由管理”一节中讲过:Material组件库中提供了一个MaterialPageRoute组件,它可以使用和平台风格一致的路由切换动画,如在iOS上会左右滑动切换,而在Android上会上下滑动切换

我们如果在Android上也想使用左右切换风格,该怎么做?一个简单的作法是可以直接使用CupertinoPageRoute,如:

  1. Navigator.push(context, CupertinoPageRoute(
  2. builder: (context)=>PageB(),
  3. ));

CupertinoPageRoute是Cupertino组件库提供的iOS风格的路由切换组件,它实现的就是左右滑动切换。那么我们如何来自定义路由切换动画呢?答案就是PageRouteBuilder。下面我们来看看如何使用PageRouteBuilder来自定义路由切换动画。例如我们想以渐隐渐入动画来实现路由过渡,实现代码如下:

  1. import 'package:flutter/cupertino.dart';
  2. import 'package:flutter/material.dart';
  3. void main() => runApp(MyApp());
  4. class MyApp extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return MaterialApp(
  8. title: '路由动画',
  9. theme: new ThemeData(
  10. primarySwatch: Colors.blue,
  11. ),
  12. home: FirstPage());
  13. }
  14. }
  15. class FirstPage extends StatelessWidget {
  16. @override
  17. Widget build(BuildContext context) {
  18. return Scaffold(
  19. backgroundColor: Colors.blue,
  20. appBar: AppBar(
  21. title: Text('FirstPage', style: TextStyle(fontSize: 36.0)),
  22. elevation: 0.0,
  23. ),
  24. body: Center(
  25. child: MaterialButton(
  26. child: Icon(
  27. Icons.navigate_next,
  28. color: Colors.white,
  29. size: 64.0,
  30. ),
  31. onPressed: () {
  32. // Navigator.of(context).push(CustomRoute(SecondPage()));
  33. Navigator.push(
  34. context,
  35. // //Andorid的上下滑动
  36. // MaterialPageRoute(
  37. // builder: (BuildContext context) => SecondPage(),
  38. // ));
  39. // // ios的左右滑动
  40. // CupertinoPageRoute(
  41. // builder: (context) => SecondPage(),
  42. // ));
  43. // //自定义路由动画
  44. CustomRoute(
  45. SecondPage(),
  46. ));
  47. },
  48. ),
  49. ));
  50. }
  51. }
  52. class CustomRoute extends PageRouteBuilder {
  53. final Widget widget;
  54. CustomRoute(this.widget)
  55. : super(
  56. transitionDuration: const Duration(seconds: 1),
  57. pageBuilder: (BuildContext context, Animation<double> animation1,
  58. Animation<double> animation2) {
  59. return ScaleTransition(
  60. scale: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
  61. parent: animation1, curve: Curves.fastOutSlowIn)),
  62. child: widget);
  63. },
  64. );
  65. }
  66. class SecondPage extends StatelessWidget {
  67. @override
  68. Widget build(BuildContext context) {
  69. return Scaffold(
  70. backgroundColor: Colors.pinkAccent,
  71. appBar: AppBar(
  72. title: Text(
  73. 'SecondPage',
  74. style: TextStyle(fontSize: 36.0),
  75. ),
  76. backgroundColor: Colors.pinkAccent,
  77. leading: Container(),
  78. elevation: 0.0,
  79. ),
  80. body: Center(
  81. child: MaterialButton(
  82. child: Icon(Icons.navigate_before, color: Colors.white, size: 64.0),
  83. onPressed: () => Navigator.of(context).pop(),
  84. ),
  85. ));
  86. }
  87. }

我们可以看到pageBuilder 有一个animation参数,这是Flutter路由管理器提供的,在路由切换时pageBuilder在每个动画帧都会被回调,因此我们可以通过animation对象来自定义过渡动画。

使用PageRoute定制路由

无论是MaterialPageRouteCupertinoPageRoute还是PageRouteBuilder,它们都继承自PageRoute类,而PageRouteBuilder其实只是PageRoute的一个包装,我们可以直接继承PageRoute类来实现自定义路由,上面的例子可以通过如下方式实现:

  1. 查看PageRouteBuilder源码 ```dart Widget _defaultTransitionsBuilder(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { return child; } class PageRouteBuilder extends PageRoute { PageRouteBuilder({ RouteSettings settings, @required this.pageBuilder, this.transitionsBuilder = _defaultTransitionsBuilder, this.transitionDuration = const Duration(milliseconds: 300), this.opaque = true, this.barrierDismissible = false, this.barrierColor, this.barrierLabel, this.maintainState = true, bool fullscreenDialog = false, }) : assert(pageBuilder != null),

    1. assert(transitionsBuilder != null),
    2. assert(opaque != null),
    3. assert(barrierDismissible != null),
    4. assert(maintainState != null),
    5. assert(fullscreenDialog != null),
    6. super(settings: settings, fullscreenDialog: fullscreenDialog);

    final RoutePageBuilder pageBuilder;

final RouteTransitionsBuilder transitionsBuilder;

@override final Duration transitionDuration;

@override final bool opaque;

@override final bool barrierDismissible;

@override final Color barrierColor;

@override final String barrierLabel;

@override final bool maintainState;

@override Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { return pageBuilder(context, animation, secondaryAnimation); }

@override Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { return transitionsBuilder(context, animation, secondaryAnimation, child); } }

  1. 2. 定义一个路由类FadeRoute
  2. ```dart
  3. class FadeRoute extends PageRoute {
  4. FadeRoute({
  5. @required this.builder,
  6. this.transitionDuration = const Duration(milliseconds: 300),
  7. this.opaque = true,
  8. this.barrierDismissible = false,
  9. this.barrierColor,
  10. this.barrierLabel,
  11. this.maintainState = true,
  12. });
  13. final WidgetBuilder builder;
  14. @override
  15. final Duration transitionDuration;
  16. @override
  17. final bool opaque;
  18. @override
  19. final bool barrierDismissible;
  20. @override
  21. final Color barrierColor;
  22. @override
  23. final String barrierLabel;
  24. @override
  25. final bool maintainState;
  26. @override
  27. Widget buildPage(BuildContext context, Animation<double> animation,
  28. Animation<double> secondaryAnimation) => builder(context);
  29. @override
  30. Widget buildTransitions(BuildContext context, Animation<double> animation,
  31. Animation<double> secondaryAnimation, Widget child) {
  32. return FadeTransition(
  33. opacity: animation,
  34. child: builder(context),
  35. );
  36. }
  37. }
  1. 使用FadeRoute ```dart import ‘package:flutter/cupertino.dart’; import ‘package:flutter/material.dart’;

void main() => runApp(MyApp());

class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: ‘路由动画’, theme: new ThemeData( primarySwatch: Colors.blue, ), home: FirstPage()); } }

class FirstPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.blue, appBar: AppBar( title: Text(‘FirstPage’, style: TextStyle(fontSize: 36.0)), elevation: 0.0, ), body: Center( child: MaterialButton( child: Icon( Icons.navigate_next, color: Colors.white, size: 64.0, ), onPressed: () { //使用FadeRoute Navigator.push(context, FadeRoute( builder: (BuildContext context) { return SecondPage(); }, )); }, ), )); } }

class FadeRoute extends PageRoute { FadeRoute({ @required this.builder, this.transitionDuration = const Duration(milliseconds: 300), this.opaque = true, this.barrierDismissible = false, this.barrierColor, this.barrierLabel, this.maintainState = true, }); final WidgetBuilder builder; @override final Duration transitionDuration; @override final bool opaque; @override final bool barrierDismissible; @override final Color barrierColor; @override final String barrierLabel; @override final bool maintainState; @override Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) => builder(context); @override Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { return FadeTransition( opacity: animation, child: builder(context), ); } }

class SecondPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.pinkAccent, appBar: AppBar( title: Text( ‘SecondPage’, style: TextStyle(fontSize: 36.0), ), backgroundColor: Colors.pinkAccent, leading: Container(), elevation: 0.0, ), body: Center( child: MaterialButton( child: Icon(Icons.navigate_before, color: Colors.white, size: 64.0), onPressed: () => Navigator.of(context).pop(), ), )); } }

  1. 虽然上面的两种方法都可以实现自定义切换动画,但实际使用时应优先考虑使用PageRouteBuilder,这样无需定义一个新的路由类,使用起来会比较方便。
  2. 但是有些时候PageRouteBuilder是不能满足需求的,例如在应用过渡动画时我们需要读取当前路由的一些属性,这时就只能通过继承PageRoute的方式了,举个例子
  3. 假如我们只想在打开新路由时应用动画,而在返回时不使用动画,那么我们在构建过渡动画时就必须判断当前路由isActive属性是否为true,代码如下:
  4. ```dart
  5. @override
  6. Widget buildTransitions(BuildContext context, Animation<double> animation,
  7. Animation<double> secondaryAnimation, Widget child) {
  8. //当前路由被激活,是打开新路由
  9. if(isActive) {
  10. return FadeTransition(
  11. opacity: animation,
  12. child: builder(context),
  13. );
  14. }else{
  15. //是返回,则不应用过渡动画
  16. return Padding(padding: EdgeInsets.zero);
  17. }
  18. }

关于路由参数的详细信息读者可以自行查阅API文档,比较简单,不再赘述。读者可以去看更多好用的路由动画链接