Flutter开发(15)- 路由导航

我们通常会用屏(Screen)来称呼一个页面(Page),一个完整的App应该是有多个Page组成的。

在之前的案例(豆瓣)中,我们通过IndexedStack来管理了首页中的Page切换:

    首页-书影音-小组-市集-我的

    通过点击BottomNavigationBarItem来设置IndexedStack的index属性来切换

除了上面这种管理页面的方式,我们还需要实现其它功能的页面跳转:比如点击一个商品跳转到详情页,某个按钮跳转到发送朋友圈、微博的编辑页面。

这种页面的管理和导航,我们通常会使用路由进行统一管理。

一. 路由管理

1.1. 认识Flutter路由

路由的概念由来已久,包括网络路由、后端路由,到现在广为流行的前端路由。

    无论路由的概念如何应用,它的核心是一个路由映射表

    比如:名字 detail 映射到 DetailPage 页面等

    有了这个映射表之后,我们就可以方便的根据名字来完成路由的转发(在前端表现出来的就是页面跳转)

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

1.2. Route

Route:一个页面要想被路由统一管理,必须包装为一个Route

    官方的说法很清晰:An abstraction for an entry managed by a Navigator.

但是Route是一个抽象类,所以它是不能实例化的

    在上面有一段注释,让我们查看MaterialPageRoute来使用

/// See [MaterialPageRoute] for a route that replaces the

/// entire screen with a platform-adaptive transition.

abstractclass Route<T> {

}

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

    MaterialPageRoute在不同的平台有不同的表现

    对Android平台,打开一个页面会从屏幕底部滑动到屏幕的顶部,关闭页面时从顶部滑动到底部消失

    对iOS平台,打开一个页面会从屏幕右侧滑动到屏幕的左侧,关闭页面时从左侧滑动到右侧消失

    当然,iOS平台我们也可以使用CupertinoPageRoute

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

1.3. Navigator

Navigator:管理所有的Route的Widget,通过一个Stack来进行管理的

    官方的说法也很清晰:A widget that manages a set of child widgets with a stack discipline.

那么我们开发中需要手动去创建一个Navigator吗?

    并不需要,我们开发中使用的MaterialApp、CupertinoApp、WidgetsApp它们默认是有插入Navigator的

    所以,我们在需要的时候,只需要直接使用即可

Navigator.of(context)

Navigator有几个最常见的方法:

// 路由跳转:传入一个路由对象

Future<T> push<T extendsObject>(Route<T> route)

// 路由跳转:传入一个名称(命名路由)

Future<T> pushNamed<T extendsObject>(

  String routeName, {

    Object arguments,

  })

// 路由返回:可以传入一个参数

bool pop<T extendsObject>([ T result ])

二. 路由基本使用

1.1. 基本跳转

我们来实现一个最基本跳转:

    创建首页页面,中间添加一个按钮,点击按钮跳转到详情页面

    创建详情页面,中间添加一个按钮,点击按钮返回到首页页面

核心的跳转代码如下(首页中代码):

// RaisedButton代码(只贴出核心代码)

RaisedButton(

  child: Text("打开详情页"),

  onPressed: () => _onPushTap(context),

),

// 按钮点击执行的代码

_onPushTap(BuildContext context) {

  Navigator.of(context).push(MaterialPageRoute(

    builder: (ctx) {

      return DetailPage();

    }

  ));

}

核心的返回代码如下(详情页中代码):

// RaisedButton代码(只贴出核心代码)

RaisedButton(

  child: Text("返回首页"),

  onPressed: () => _onBackTap(context),

)

// 按钮点击执行的代码

_onBackTap(BuildContext context) {

  Navigator.of(context).pop();

}

1.2. 参数传递

在跳转过程中,我们通常可能会携带一些参数,比如

    首页跳到详情页,携带一条信息:a home message

    详情页返回首页,携带一条信息:a detail message

首页跳转核心代码:

    在页面跳转时,会返回一个Future

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

_onPushTap(BuildContext context) {

  // 1.跳转代码

  final future = Navigator.of(context).push(MaterialPageRoute(

    builder: (ctx) {

      return DetailPage("a home message");

    }

  ));

  // 2.获取结果

  future.then((res) {

    setState(() {

      _message = res;

    });

  });

}

详情页返回核心代码:

_onBackTap(BuildContext context) {

  Navigator.of(context).pop("a detail message");

}

1.3. 返回细节

但是这里有一个问题,如果用户是点击右上角的返回按钮,如何监听呢?

方法一:自定义返回的按钮(在详情页中修改Scaffold的appBar)

appBar: AppBar(

  title: Text("详情页"),

  leading: IconButton(

    icon: Icon(Icons.arrow_back),

    onPressed: () {

      Navigator.of(context).pop("a back detail message");

    },

  ),

),

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

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

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

    true:那么系统会自动帮我们执行pop操作

    false:系统不再执行pop操作,需要我们自己来执行

return WillPopScope(

  onWillPop: () {

    Navigator.of(context).pop("a back detail message");

    return Future.value(false);

  },

  child: Scaffold(

    appBar: AppBar(

      title: Text("详情页"),

    ),

    body: Center(

      child: Column(

        mainAxisAlignment: MainAxisAlignment.center,

        children: <Widget>[

          RaisedButton(

            child: Text("返回首页"),

            onPressed: () => _onBackTap(context),

          ),

          Text(_message, style: TextStyle(fontSize: 20, color: Colors.red),)

        ],

      ),

    ),

  ),

);

三. 命名路由使用

3.1. 基本跳转

我们可以通过创建一个新的Route,使用Navigator来导航到一个新的页面,但是如果在应用中很多地方都需要导航到同一个页面(比如在开发中,首页、推荐、分类页都可能会跳到详情页),那么就会存在很多重复的代码。

在这种情况下,我们可以使用命名路由(named route)

    命名路由是将名字和路由的映射关系,在一个地方进行统一的管理

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

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

    initialRoute:设置应用程序从哪一个路由开始启动,设置了该属性,就不需要再设置home属性了

    routes:定义名称和路由之间的映射关系,类型为Map<String, WidgetBuilder>

修改MaterialApp中的代码:

return MaterialApp(

  title: 'Flutter Demo',

  theme: ThemeData(

    primarySwatch: Colors.blue, splashColor: Colors.transparent

  ),

  initialRoute: "/",

  routes: {

    "/home": (ctx) => HYHomePage(),

    "/detail": (ctx) => HYDetailPage()

  },

);

修改跳转的代码:

_onPushTap(BuildContext context) {

  Navigator.of(context).pushNamed("/detail");

}

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

class HYHomePage extends StatefulWidget {

  staticconstString routeName = "/home";

}

class HYDetailPage extends StatelessWidget {

  staticconstString routeName = "/detail";

}

修改MaterialApp中routes的key

initialRoute: HYHomePage.routeName,

routes: {

  HYHomePage.routeName: (ctx) => HYHomePage(),

  HYDetailPage.routeName: (ctx) => HYDetailPage()

},

3.2. 参数传递

因为通常命名路由,我们会在定义路由时,直接创建好对象,比如HYDetailPage()

那么,命名路由如果有参数需要传递呢?

pushNamed时,如何传递参数:

_onPushTap(BuildContext context) {

  Navigator.of(context).pushNamed(HYDetailPage.routeName, arguments: "a home message of naned route");

}

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

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

  Widget build(BuildContext context) {

    // 1.获取数据

    final message = ModalRoute.of(context).settings.arguments;

  }

3.3. 路由钩子

3.3.1. onGenerateRoute

假如我们有一个HYAboutPage,也希望在跳转时,传入对应的参数message,并且已经有一个对应的构造方法

在HYHomePage中添加跳转的代码:

RaisedButton(

  child: Text("打开关于页"),

  onPressed: () {

    Navigator.of(context).pushNamed(HYAboutPage.routeName, arguments: "a home message");

  },

)

HYAboutPage的代码:

class HYAboutPage extends StatelessWidget {

  staticconstString routeName = "/about";

  finalString message;

  HYAboutPage(this.message);

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text("关于页面"),

      ),

      body: Center(

        child: Text(message, style: TextStyle(fontSize: 30, color: Colors.red),),

      ),

    );

  }

}

但是我们继续使用routes中的映射关系,就不好进行配置了,因为HYAboutPage必须要求传入一个参数;

这个时候我们可以使用onGenerateRoute的钩子函数:

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

    我们可以在该函数中,手动创建对应的Route进行返回;

    该函数有一个参数RouteSettings,该类有两个常用的属性:

        name: 跳转的路径名称

        arguments:跳转时携带的参数

onGenerateRoute: (settings) {

  if (settings.name == "/about") {

    return MaterialPageRoute(

      builder: (ctx) {

        return HYAboutPage(settings.arguments);

      }

    );

  }

  returnnull;

},

关于页面跳转

3.3.2. onUnknownRoute

如果我们打开的一个路由名称是根本不存在,这个时候我们希望跳转到一个统一的错误页面。

比如下面的abc是不存在有对应的页面的

    如果没有进行特殊的处理,那么Flutter会报错。

RaisedButton(

  child: Text("打开未知页面"),

  onPressed: () {

    Navigator.of(context).pushNamed("/abc");

  },

)

我们可以创建一个错误的页面:

class UnknownPage extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text("错误页面"),

      ),

      body: Container(

        child: Center(

          child: Text("页面跳转错误"),

        ),

      ),

    );

  }

}

并且设置onUnknownRoute

onUnknownRoute: (settings) {

  return MaterialPageRoute(

    builder: (ctx) {

      return UnknownPage();

    }

  );

},

微信扫一扫

关注该公众号

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容