一. 路由管理

1.1. 认识Flutter路由

  • 无论路由的概念如何应用,核心是一个路由映射表
  • 比如:名字 detail 映射到 DetailPage 页面等
  • 有了映射表之后,就可以方便的根据名字来完成路由的转发

在Flutter中,路由管理主要有两个类:Route和Navigator

1.2. Route

Route:一个页面要想被路由统一管理,必须包装为一个Route
但Route是一个抽象类,所以不能实例化

  1. abstractclass Route<T> {
  2. }

事实上MaterialPageRoute并不是Route的直接子类:

  • MaterialPageRoute在不同的平台有不同的表现
  • 对Android平台,打开一个页面会从屏幕底部滑动到屏幕的顶部,关闭页面时从顶部滑动到底部消失
  • 对iOS平台,打开一个页面会从屏幕右侧滑动到屏幕的左侧,关闭页面时从左侧滑动到右侧消失
  • 当然,iOS平台我们也可以使用CupertinoPageRoute

MaterialPageRoute -> PageRoute -> ModalRoute -> TransitionRoute -> OverlayRoute -> Route

1.3. Navigator

Navigator:管理所有的Route的Widget,通过一个Stack来进行管理的,开发中需要手动去创建一个Navigator吗?

  • MaterialApp、CupertinoApp、WidgetsApp它们默认是有插入Navigator的
  • 直接使用即可
    1. Navigator.of(context)
    Navigator有几个最常见的方法: ```dart // 路由跳转:传入一个路由对象 Future push(Route route) // 路由跳转:传入一个名称(命名路由) Future pushNamed( String routeName, { Object arguments, })

// 路由返回:可以传入一个参数 bool pop([ T result ])

  1. <a name="HpeEM"></a>
  2. ## 二. 路由基本使用
  3. <a name="UbCqD"></a>
  4. ### 1.1. 基本跳转
  5. - 首页跳转到详情页面
  6. - 详情页返回到首页
  7. 跳转代码:
  8. ```dart
  9. // RaisedButton代码
  10. RaisedButton(
  11. child: Text("打开详情页"),
  12. onPressed: () => _onPushTap(context),
  13. ),
  14. // 按钮点击执行的代码
  15. _onPushTap(BuildContext context) {
  16. Navigator.of(context).push(MaterialPageRoute(
  17. builder: (ctx) {
  18. return DetailPage();
  19. }
  20. ));
  21. }

返回代码:

  1. // RaisedButton代码(只贴出核心代码)
  2. RaisedButton(
  3. child: Text("返回首页"),
  4. onPressed: () => _onBackTap(context),
  5. )
  6. // 按钮点击执行的代码
  7. _onBackTap(BuildContext context) {
  8. Navigator.of(context).pop();
  9. }

1.2. 参数传递

跳转过程中,通常可能会携带一些参数:
首页跳转核心代码:

  • 在页面跳转时,会返回一个Future
  • 该Future会在详情页面调用pop时,回调对应的then函数,并且会携带结果

    1. _onPushTap(BuildContext context) {
    2. // 1.跳转代码
    3. final future = Navigator.of(context).push(MaterialPageRoute(
    4. builder: (ctx) {
    5. return DetailPage("a home message");
    6. }
    7. ));
    8. // 2.获取结果
    9. future.then((res) {
    10. setState(() {
    11. _message = res;
    12. });
    13. });
    14. }

    详情页返回核心代码:

    1. _onBackTap(BuildContext context) {
    2. Navigator.of(context).pop("a detail message");
    3. }

    1.3. 返回细节

    如果用户是点击右上角的返回按钮,如何监听呢?
    方法一:自定义返回的按钮(在详情页中修改Scaffold的appBar)

    1. appBar: AppBar(
    2. title: Text("详情页"),
    3. leading: IconButton(
    4. icon: Icon(Icons.arrow_back),
    5. onPressed: () {
    6. Navigator.of(context).pop("a back detail message");
    7. },
    8. ),
    9. ),

    方法二:监听返回按钮的点击(给Scaffold包裹一个WillPopScope)

  • WillPopScope有一个onWillPop的回调函数,当点击返回按钮时会执行

  • 这个函数要求有一个Future的返回值:

    • true:那么系统会自动帮我们执行pop操作
    • false:系统不再执行pop操作,需要我们自己来执行
      1. return WillPopScope(
      2. onWillPop: () {
      3. Navigator.of(context).pop("a back detail message");
      4. return Future.value(false);
      5. },
      6. child: Scaffold(
      7. appBar: AppBar(
      8. title: Text("详情页"),
      9. ),
      10. body: Center(
      11. child: Column(
      12. mainAxisAlignment: MainAxisAlignment.center,
      13. children: <Widget>[
      14. RaisedButton(
      15. child: Text("返回首页"),
      16. onPressed: () => _onBackTap(context),
      17. ),
      18. Text(_message, style: TextStyle(fontSize: 20, color: Colors.red),)
      19. ],
      20. ),
      21. ),
      22. ),
      23. );

      三. 命名路由使用

      3.1. 基本跳转

      可以通过创建一个新的Route,使用Navigator来导航到一个新的页面,但如果在应用中很多地方都需要导航到同一个页面(比如在开发中,首页、推荐、分类页都可能会跳到详情页),那么就会存在很多重复的代码,可以使用命名路由(named route):
  • 命名路由是将名字和路由的映射关系,在一个地方进行统一的管理

  • 有了命名路由,可以通过Navigator.pushNamed() 方法来跳转到新的页面

命名路由在哪里管理呢?可以放在MaterialApp的 initialRoute 和 routes 中

  • initialRoute:设置应用程序从哪一个路由开始启动,设置了该属性,就不需要再设置home属性了
  • routes:定义名称和路由之间的映射关系,类型为Map

MaterialApp中的代码:

  1. return MaterialApp(
  2. title: 'Flutter Demo',
  3. theme: ThemeData(
  4. primarySwatch: Colors.blue, splashColor: Colors.transparent
  5. ),
  6. initialRoute: "/",
  7. routes: {
  8. "/home": (ctx) => HYHomePage(),
  9. "/detail": (ctx) => HYDetailPage()
  10. },
  11. );

修改跳转的代码:

  1. _onPushTap(BuildContext context) {
  2. Navigator.of(context).pushNamed("/detail");
  3. }

开发中,为了让每个页面对应的routeName统一,通常会在每个页面中定义一个路由的常量来使用:

  1. class HYHomePage extends StatefulWidget {
  2. staticconstString routeName = "/home";
  3. }
  4. class HYDetailPage extends StatelessWidget {
  5. staticconstString routeName = "/detail";
  6. }

修改MaterialApp中routes的key

  1. initialRoute: HYHomePage.routeName,
  2. routes: {
  3. HYHomePage.routeName: (ctx) => HYHomePage(),
  4. HYDetailPage.routeName: (ctx) => HYDetailPage()
  5. },

3.2. 参数传递

因为通常命名路由,会在定义路由时,直接创建好对象,比如WLDetailPage(),那么,命名路由如果有参数需要传递呢?

  1. _onPushTap(BuildContext context) {
  2. Navigator.of(context).pushNamed(HYDetailPage.routeName, arguments: "a home message of naned route");
  3. }

在WLDetailPage中,如何获取到参数呢?

  • 在build方法中ModalRoute.of(context)可以获取到传递的参数

    1. Widget build(BuildContext context) {
    2. // 1.获取数据
    3. final message = ModalRoute.of(context).settings.arguments;
    4. }

    3.3. 路由钩子

    3.3.1. onGenerateRoute

    假如有一个WLAboutPage,也希望在跳转时,传入对应的参数message,并且已经有一个对应的构造方法
    在HYHomePage中添加跳转的代码:

    1. RaisedButton(
    2. child: Text("打开关于页"),
    3. onPressed: () {
    4. Navigator.of(context).pushNamed(HYAboutPage.routeName, arguments: "a home message");
    5. },
    6. )

    HYAboutPage的代码:

    1. class WLAboutPage extends StatelessWidget {
    2. staticconstString routeName = "/about";
    3. finalString message;
    4. HYAboutPage(this.message);
    5. @override
    6. Widget build(BuildContext context) {
    7. return Scaffold(
    8. appBar: AppBar(
    9. title: Text("关于页面"),
    10. ),
    11. body: Center(
    12. child: Text(message, style: TextStyle(fontSize: 30, color: Colors.red),),
    13. ),
    14. );
    15. }
    16. }

    但是我们继续使用routes中的映射关系,就不好进行配置了,因为HYAboutPage必须要求传入一个参数;
    这个时候我们可以使用onGenerateRoute的钩子函数:

  • 当我们通过pushNamed进行跳转,但是对应的name没有在routes中有映射关系,那么就会执行onGenerateRoute钩子函数;

  • 我们可以在该函数中,手动创建对应的Route进行返回;
  • 该函数有一个参数RouteSettings,该类有两个常用的属性:

    • name: 跳转的路径名称
    • arguments:跳转时携带的参数
      1. onGenerateRoute: (settings) {
      2. if (settings.name == "/about") {
      3. return MaterialPageRoute(
      4. builder: (ctx) {
      5. return HYAboutPage(settings.arguments);
      6. }
      7. );
      8. }
      9. returnnull;
      10. },

      3.3.2. onUnknownRoute

      如果打开的一个路由名称是根本不存在,这个时候希望跳转到一个统一的错误页面。
  • 如果没有进行特殊的处理,那么Flutter会报错

    1. RaisedButton(
    2. child: Text("打开未知页面"),
    3. onPressed: () {
    4. Navigator.of(context).pushNamed("/abc");
    5. },
    6. )

    创建一个错误的页面:

    1. class UnknownPage extends StatelessWidget {
    2. @override
    3. Widget build(BuildContext context) {
    4. return Scaffold(
    5. appBar: AppBar(
    6. title: Text("错误页面"),
    7. ),
    8. body: Container(
    9. child: Center(
    10. child: Text("页面跳转错误"),
    11. ),
    12. ),
    13. );
    14. }
    15. }

    并且设置onUnknownRoute

    1. onUnknownRoute: (settings) {
    2. return MaterialPageRoute(
    3. builder: (ctx) {
    4. return UnknownPage();
    5. }
    6. );
    7. },