博主的博客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的入口方法:
void main() {
runApp(MyApp());///程序入口
}
class MyApp extends StatelessWidget {
@override
Widget 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 {
@override
Widget 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 {
@override
Widget 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对象
PageTwo页面的主要功能是,点击页面的时候跳转到DefalutPage页面!但是其点击事件有个需要注意的地方,下面看源码:
class PageTwo extends StatelessWidget {
const PageTwo({
this.onSignupComplete,
});
final VoidCallback onSignupComplete;
@override
Widget build(BuildContext context) {
return GestureDetector(
//点击事件
onTap:onSignupComplete,
child: DefaultTextStyle(
child: Text('Page Two'),
),
);
}
}
注意看上面PageTwo源码中build方法中的点击事件,我们是在InitPage中初始化的,代码如下(在这里称之为方式A):
///方式A
case 'pageTwo':
builder = (BuildContext ctx) => PageTwo(
点击事件
onSignupComplete: () {Navigator.of(context).pop();},
);
break
为什么不直接写成如下所示呢?(在这里称之为方式B):
//方式B
Widget 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 {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Code Sample for Navigator',
// MaterialApp contains our top-level Navigator
initialRoute: '/initPage',
routes: {
'/': (BuildContext context) => DefaultPage(),
'/initPage': (BuildContext context) => InitPage(),
},
);
}
}
class InitPage extends StatelessWidget {
@override
Widget 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 {
@override
Widget 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 {
@override
Widget 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;
@override
Widget 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'),
),
),
);
}
}