博主的博客Flutter之路由系列之LocalHistoryRoute简单的梳理下Flutter的路由机制,其中Navigator扮演者重要的角色。本篇博文就简单梳理下Navigator的相关知识点。闲言少叙,开始发车。
通过本篇博客你可以了解到:
1、MaterialApp内置了一个Navigator对象
2、一个APP中有多个Navigator对象在调用Navigator.of(context)要注意的事项
本篇通过一个demo app来说明Navigator的用法及其细节,在我们的demo中有个四个页面:InitPage,PageOne,PageTwo,DefaultPage。
其中PageOne,PageTow和DefalutPage三个页面及其简单,就是在屏幕总展示了“Page One”,“Page Two”和“Defalut Page”的文本文字,比如Default Page的页面效果如下:
在我本篇的demo中(博文最后提供全部源码),启动的时候会先展示InitPage,然后自动展示PageOne,点击PageOne,跳到PageTwo,点击PageTwo,跳到DefaultPage。关于这几个页面的关系,咱们一步步分析,先来看看demo的入口方法:
1、配置MaterialApp的初始路由和路由表
void main() {runApp(MyApp());///程序入口}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(initialRoute: '/initPage',routes: {'/': (BuildContext context) => DefaultPage(),'/initPage': (BuildContext context) => InitPage(),},);}}
关于MaterialApp有一个要注意的地方:它包含了APP中的top level 或者全局的Navigator对象,因为MaterialApp其内部内置了Navigator对象。另外MyApp还做了如下工作:
1、初始化了MaterialApp的initialRoute属性,该属性为String类型,代表着初始路由的Name
2、配置了MaterialApp的路由表routes属性,该属性是一个Map集合,key代表着路由的Name,Value代表着对应的Page
配置routes的时候,DefaultPage对应的路由名字是"/",代表默认路由;而InitPage对应的路由名字是“/initPage”,因为initialRoute属性设置为“/initPage”,所以我们的demo app运行起来的时候,首先展示的就是InitPage,所以来看看InitPage是什么。
注意此时内置的路由表的有两个路由,一个是默认路由“/”,一个是“/initPage”路由。
2、InitPage中Navigator使用方法
因为Navigator也是是个Widget,确切的来说是一个StatefulWidget.所以在InitPage中我们直接使用了Navigator作为UI展示,InitPage的代码如下:
class InitPage extends StatelessWidget {@overrideWidget build(BuildContext context) {///直接返回了一个Navigator对象return Navigator(//初始路由的名字initialRoute: 'pageOne',onGenerateRoute: (RouteSettings settings) {WidgetBuilder builder;switch (settings.name) {case 'pageOne'://pageOne的路由对应的页面builder = (BuildContext ctx) => PageOne();break;case 'pageTwo'://pageTwo的路由对应的页面builder = (BuildContext ctx) => PageTwo(onSignupComplete: () {///PageTwo点击事件,跳转到DefaultPage页面//注意这个Navigator.of操作的是MaterialApp的内置Navigator,后面会有说明Navigator.of(context).pop();},);break;}return MaterialPageRoute(builder: builder, settings: settings););}}
可以看出InitPage的build方法直接返回了Navigator,通过上面的的讲解我们知道MaterialApp也内置了一个Navigator对象,那么问题来了:
InitPage的Navigator对象和MaterialApp的Navigator对象,有啥区别和联系呢?这个问题会在后面的分析中说明,在这里读者可以留个心眼.
在InitPage 中我们为Navigator配置了如下属性:
1、initialRoute设置为pageOne
2、onGenerateRoute:根据路有配置RouteSettings 来展示不同的页面Page One 和Page Two,因为initialRoute配置为pageOne,所以APP 启动的时候就优先展示Page One.:
当我们调用Navigator.of(context).pushReplacementNamed(‘pageTwo’)或者Navigator.of(context).pushNamed(‘pageTwo’)的时候,就会走onGenerateRoute的case 'pageTwo'
分支:
case 'pageTwo'://pageTwo的路由对应的页面builder = (BuildContext ctx) => PageTwo(onSignupComplete: () {///PageTwo点击事件,跳转到DefaultPage页面//注意这个Navigator.of操作的是MaterialApp的内置Navigator,后面会有说明Navigator.of(context).pop();},);break;
3、onGenerateRoute最终返回了MaterialPageRoute
2.1 PageOne的简单实现
再来看看PageOne的代码,PageOne页面展示了“Page One”文本和一个点击事件,当点击的时候跳转到PageTwo页面:
class PageOne extends StatelessWidget {@overrideWidget build(BuildContext context) {return DefaultTextStyle(child: GestureDetector(onTap: () {///替换PageOne页面,展示PageTwo//注意这个Navigator.of操作的是InitPage的Navigator,后面会有说明Navigator.of(context).pushNamed('pageTwo');},child: Text('Page One'),),);}}
在PageOne中调用了Navigator.of(context).pushReplacementNamed('pageTwo')
来展示Page Two,注意这个Navigator.of(Context)返回的对象是InitPage包含的那个Navigator对应的NavigatorState对象,而不是MaterialApp内置Navigator对应的NavigatorState对象
2.2 PageTwo的简单实现
PageTwo页面的主要功能是,点击页面的时候跳转到DefalutPage页面!但是其点击事件有个需要注意的地方,下面看源码:
class PageTwo extends StatelessWidget {const PageTwo({this.onSignupComplete,});final VoidCallback onSignupComplete;@overrideWidget build(BuildContext context) {return GestureDetector(//点击事件onTap:onSignupComplete,child: DefaultTextStyle(child: Text('Page Two'),),);}}
注意看上面PageTwo源码中build方法中的点击事件,我们是在InitPage中初始化的,代码如下(在这里称之为方式A):
///方式Acase 'pageTwo':builder = (BuildContext ctx) => PageTwo(点击事件onSignupComplete: () {Navigator.of(context).pop();},);break
为什么不直接写成如下所示呢?(在这里称之为方式B):
//方式BWidget build(BuildContext context) {return GestureDetector(//点击事件onTap: () {Navigator.of(context).pop();},child: DefaultTextStyle(child: Text('Page Two'),),);}
原因很简单,方式A和方式B的点击代码看起来完全一样都是执行Navigator.of(context).pop()
,但是二者有着本质的区别:
方式A的Navigator.of(context)不等于方式B的Navigator.of(context)。更确切的来说是方式A的Navigator.of(context)返回的NavigatorState对象是属于MaterialApp内置的Navigator对象的,而方式B返回的Navigator.of(context)是属于InitPage包含的Navigator对象的,更直观的可以用下面代码来说明二者的区别:
如上图所以Navigator.of(context).pop(),是将InitPage弹出了路由,所以会展示DefaultPage.
而Navigator.of(ctx).pop()处理的是InitPage的路由逻辑,所以此时会将PageTwo弹出,而展示PageOne(因为前文说了在InitPage中先展示的是PageOne)。
到此为止Navigator的简单实用讲解完毕,如有不当之处,欢迎批评指正。下面是demo app的源码:
class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Code Sample for Navigator',// MaterialApp contains our top-level NavigatorinitialRoute: '/initPage',routes: {'/': (BuildContext context) => DefaultPage(),'/initPage': (BuildContext context) => InitPage(),},);}}class InitPage extends StatelessWidget {@overrideWidget build(BuildContext context) {print("context=="+context.toString());// SignUpPage builds its own Navigator which ends up being a nested// Navigator in our app.return Navigator(initialRoute: 'pageOne',onGenerateRoute: (RouteSettings settings) {WidgetBuilder builder;switch (settings.name) {case 'pageOne':builder = (BuildContext ctx) => PageOne();break;case 'pageTwo':builder = (BuildContext ctx) => PageTwo(onSignupComplete: () {Navigator.of(context).pop();},);break;}return MaterialPageRoute(builder: builder, settings: settings);},);}}class DefaultPage extends StatelessWidget {@overrideWidget build(BuildContext context) {print("NavigatorpageTwo"+Navigator.of(context).toString());return DefaultTextStyle(style: Theme.of(context).textTheme.headline6,child: Container(color: Colors.white,alignment: Alignment.center,child: Text('Default Page'),),);}}class PageOne extends StatelessWidget {@overrideWidget build(BuildContext context) {return DefaultTextStyle(style: Theme.of(context).textTheme.headline6,child: GestureDetector(onTap: () {print("Navigator"+Navigator.of(context).toString());Navigator.of(context).pushNamed('pageTwo');},child: Container(color: Colors.lightBlue,alignment: Alignment.center,child: Text('Page One'),),),);}}class PageTwo extends StatelessWidget {const PageTwo({this.onSignupComplete,});final VoidCallback onSignupComplete;@overrideWidget build(BuildContext context) {return GestureDetector(onTap:onSignupComplete,child: DefaultTextStyle(style: Theme.of(context).textTheme.headline6,child: Container(color: Colors.pinkAccent,alignment: Alignment.center,child: Text('Page Two'),),),);}}