兄弟们,咱们又见面了。
在上一章中,咱们入门学习了Flutter神功,会了些皮毛,知道了输入框,单选复选等这些基础组件的使用,小生并没有讲解按钮这种基础组件的使用,像这种easy的不能再easy的组件知识点,如果你看了还不会,那么请照一下镜子,告诉自己:放弃编程吧。
万丈红尘财路多,何必非做一码农?
咱们已经学会了Flutter的一些皮毛,在一个页面里,咱们能够整个输入框,整个按钮了,咱们很高兴,就想着大展身手,多整几个页面,这时,懵了,我怎么构建其他页面?我怎么打开其他页面?
这时候,就用到了路由的概念。
什么是路由?
路由(Route)在移动开发中通常指页面(Page),这跟web开发中单页应用的Route概念意义是相同的,Route在Android中通常指一个Activity,在iOS中指一个ViewController。
什么是路由管理?
所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。Flutter中的路由管理和原生开发类似,无论是Android还是iOS,导航管理都会维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。
兄弟们对上面的概念可能有点模糊,别担心,看完下面的内容就明白了。
咱们先创建三个页面,page1.dart,page2.dart,page3.dart。
每个页面的基本结构如下:
import 'package:flutter/material.dart';
class PageOne extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold( // 一个页面骨架容器类型组件,在这里可以设置导航栏,主要页面等内容
appBar: AppBar(
title: Text("第一个页面"),
),
body: Column(
children: <Widget>[
RaisedButton(
child: Text("跳转第二页"),
onPressed: (){
// 这里执行按钮的点击事件,如跳转页面,获取数据等操作
}
)
],
),
);
}
}
Scaffold组件,兄弟们可以认为就是一个页面骨架组件,创建页面,就用这个组件就行,详细内容,小生后续章节讲解。
好,页面咱们已经创建了,下一步就是要让这个页面显示出来。
兄弟们别忘了一个概念:组件。
按钮是组件,输入框是组件,图片是组件,你创建的这个.dart文件也可以看成组件。并不是官方提供的东西才是组件,你创建的.dart文件,可以是页面,也可以是按钮,输入框等,或者自定义的内容,都可以看成组件。
既然是组件,那就可以引入。
兄弟们在你们的main.dart里面将page1.dart引入,不会引入?开什么玩笑,你敲一个import就会有提示,无论是../还是./或者还是直接lib/这种,只要你能引入进来不报错就行,引入个两三遍你就心领神会了。
引入后,在页面中添加一个按钮,在按钮的onPressed事件中添加以下操作:
//导航到新路由
Navigator.push( context,
MaterialPageRoute(builder: (context) {
return PageOne(); // 你每个页面的类名是什么,这里就是什么
}));
兄弟们又懵了,这又是Navigator又是MaterialPageRoute,这都是什么玩意?就这么写就行吗?
对,就这么写就行,不信你点点按钮,啪嚓,直接跳转到了page1.dart这个页面了。
先来说下MaterialPageRoute这个东西,通俗一点讲,这玩意提供了显示路由页面的功能,比如说,你怎么显示页面?用什么动画效果显示页面?显示哪一个页面?都可以用这玩意来操作,如果兄弟们觉得小生说的不够官方,不够学术,你就搜一搜官方文档,这里不多做解释。
啥是Navigator?
Navigator是一个路由管理的组件,它提供了打开和退出路由页方法。Navigator通过一个栈来管理活动路由集合。通常当前屏幕显示的页面就是栈顶的路由。Navigator提供了一系列方法来管理路由栈,比如push,pop等。
有朋友又迷惑了,什么是栈?学Flutter怎么还整上客栈了?
这个栈啊,兄弟们可以认为是一个盒子,打个比方,你偷偷给你女朋友写情书,写完一张往这个盒子里放一张,你看到的永远都是最上面的一张,也就是你当前看到的页面,你往这个盒子里一张张放情书的过程就是入栈push,你一张张拿出来的过程就是出栈pop。
Navigator就是Flutter为咱们提供的控制页面打开关闭的一个工具,有朋友恍然大悟:哦~原来是这么回事啊!我既然能够打开一个页面了,那就可以打开第二个,第三个页面,我直接往页面里引入其他页面不就完了吗?
理是这个理,但是当页面多的时候,你就会看出来问题来了,比如有几十个页面,一个页面引入好几个页面路径,一个页面又被好几个页面引用,这就导致页面内有大量的引用,从代码层面来讲,并不是一件比较合理的事情,这时,咱们就可以用到命名路由的方案。
何为命名路由?
所谓“命名路由”(Named Route)即有名字的路由,我们可以先给路由起一个名字,然后就可以通过路由名字直接打开新的路由了,这为路由管理带来了一种直观、简单的方式。
就像一个班级里,有叫张三的,有叫李四的,每一个学生看成路由页的话,那么名字就是唤起这个路由页的代号,老师手中有一份学生的名册,需要哪个学生回答问题,就点哪个学生的名字。
放在Flutter中,这个名册就是路由表,通过路由表就可以唤起各个路由页面。
要想让这个路由表生效,咱们需要再对应的位置,将这个路由表注册声明一下才行,就是告诉Flutter应用,我是路由表,你想切换页面,找我就行。
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
//注册路由表
routes:{
"/page1":(context) => PageOne(), // 先引入import对应的页面文件,再再此声明
"/page2":(context) => PageTwo(),
... // 省略其它路由注册信息
} ,
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
在注册路由表的时候,/page1,/page2就是对应的路由页面的名称,你可以采用这种路径的命名方式,也可以直接使用名字,比如:page1。
学过Vue的朋友肯定对路由不陌生,这里推荐使用路径的命名方式,比较直观直白,这里Web门派的朋友们需要注意一点,上述路由表中,如果采用路径命名的方式,/page1就是一个完整的名字而不是page1。
为了方便管理,咱们可以把所有的路由路径单独提出来,抽成一个单独的routers.dart文件。
import 'package:flutter/material.dart';
import '../wedgets/myHome.dart';
import '../wedgets/routerDeme/page1.dart';
import '../wedgets/routerDeme/page2.dart';
import '../wedgets/routerDeme/page3.dart';
final routes = {
'/': (context) => MyHomePage(),
'/page1': (context) => PageOne(),
'/page2': (context) => PageTwo(),
'/page3': (context) => PageThree(),
};
main.dart如下:
import 'package:flutter/material.dart';
import 'router/routers.dart'; // 引入路由文件的路径
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/', // 这里控制默认显示的首页,/视为根路径,比如:/login,/home等都可
routes: routes,
);
}
}
Web门派的兄弟们肯定恍然大悟,唉呀妈呀,这不是和Vue中的路由差不多嘛!是滴,差不多。
现在路由表很方便的创建了,我想要啥页面,就往里面添加对应的路由名即可,这时,咱们就可以进行路由跳转操作了。
因为咱们已经给对应的路由命名了,所以不需要再在每个页面引入路径了,这时,上面那种路由跳转的方式就不能用了,可以用pushNamed方法进行跳转页面操作。
Future pushNamed(BuildContext context, String routeName,{Object arguments})
onPressed: () {
Navigator.pushNamed(context, "/page1"); // Navigator.of(context).pushNamed("/page1");
//Navigator.push(context,
// MaterialPageRoute(builder: (context) {
// return PageOnw();
//}));
},
多简单,方法在手,随便跳,想跳哪儿就跳哪儿,这时,肯定有朋友疑问了,我怎么传参啊?就像兄弟们去丈母娘家,总得带点礼物不是?
这么办:
// Navigator.of(context).pushNamed('/page2', arguments: 'hello');
Navigator.pushNamed(context, '/page2', arguments: 'hello'); // arguments是固定参数,后面跟上要传的内容,比如id,type等
比如从商品列表进入商品详情页,订单列表进入订单详情等操作,都可以传个id参数。
好,兄弟们已经将一盒包装精美的茶叶带到了老丈人家,这老丈人得拆开盒子才能拿到茶叶是不是?怎么拆,怎么拿到茶叶?
class PageTwoextends StatelessWidget {
@override
Widget build(BuildContext context) {
// 获取路由参数,返回字符串
var args=ModalRoute.of(context).settings.arguments;
//...省略无关代码
}
}
就通过上面的方法,你就可以拿到上一页传过来的数据,当然,你也可以通过在目标页面添加自定义参数来接收传值,这种方法兄弟们可查看中文网示例,这里不做讲解。
咱们跳了一个又一个页面,是不是还要回去啊?就像是你去了你媳妇家,紧接着去了你媳妇舅家,最终是不是还要回到你家。咱们怎么执行返回操作?
怎么回退上一页并返回数据?
通过pop方法来执行页面返回和向上一页返回数据操作:
// Navigator.pop(context); // Navigator.of(context).pop();
Navigator.pop(context, '返回参数');
当你点击系统自带的返回按钮或者在页面内自定义的返回时,都是执行的这个方法,但是当带上返回参数返回上一页时,如果点击的系统返回按钮,你的参数是传不到上一页的,这时,兄弟们就会有疑问,那怎么办?
兄弟们冷静下来,好好想一想需要用到向上一页返回参数的情况,比如:选择一条收货地址返回到创建订单页,实际上你执行返回操作的时候,一定会点击页面内容,而不是点击系统返回,那么你的数据就可以传到上一页。
返回到上一页后,通过此操作就可以拿到下一页返回的数据:
onPressed: () async {
// 获取下一页返回过来数据的方法,打开新页面page3后,便会等着返回,返回此页时就能拿到page3返回的数据
var res = await Navigator.pushNamed(context, '/page3');
print(res);
})
好的,兄弟们,就像上文说的,你从你家去了你媳妇家,又从你媳妇家去了你媳妇舅家,回来的时候,可以先回到你媳妇家,再回到你家,也可以直接回你家,是不是?
很多时候,当咱们跳转了好几个页面后,最终咱们点击返回,想直接返回到第一页或者首页或者某个固定的页面,这时候咱们该怎么做?
场景1:已有page1>page2>page3,从page3返回到page1。
Navigator.popUntil(context, ModalRoute.withName('/page1'));
场景2:已有page1>page2>page3,替换page3为page4。
Navigator.popAndPushNamed(context, '/page4');
Navigator.pushReplacementNamed(context, '/page4');
场景3:已有page1>page2>page3,删除所有记录,只显示page1或page2,一般page1为首页。
Navigator.pushNamedAndRemoveUntil(context, '/page1', (Route<dynamic> route) => false); // 这种方式是删除所有的路由,/page1页变成最底层的页面,这种方式可以用作返回首页
场景4:已有page1>page2>page3,显现page4页面,删除所有记录只保留page1。
Navigator.pushNamedAndRemoveUntil(context, '/page4', ModalRoute.withName('/page1')); // 从page4返回会回到page1
以上方法灵活运用,基本满足常用的页面跳转情况,当然还有一些方法小生没有列出来,贪多嚼不烂,兄弟们掌握这几个,可以自个去查询其他方法。
另外,如果你想判断当前页面是否可以退出,可以使用canPop()方法,该方法会返回一个bool值。
Navigator.of(context).canPop(); // 判断当前页面能否进行pop操作,并返回bool值
到现在为止,关于路由的大部分技能你已经掌握了,可能有朋友就会问了,我要是访问的这个页面不存在该怎么处理?
好说,请看这里:
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes, // 注册路由表
initialRoute: '/home', // 默认路由
onGenerateRoute: onGenerateRoute, // 路由拦截器
onUnknownRoute: (RouteSettings setting) { // 未知页面,404
String name = setting.name;
print("未匹配到路由:$name");
return new MaterialPageRoute(builder: (context) {
return new ErrorPage();
});
}
);
好的,兄弟们,现在进入Flutter路由的一个重头戏:路由拦截器。
路由拦截器是个啥东西?
学过Vue的朋友肯定知道路由拦截器,或者听说过路由生成钩子,在Flutter中也有这么一个东西。
兄弟们肯定遇到过这种场景:允许用户查看商城,但是需要购买的时候,没有登录跳转到登录页,因为要判断的地方可能会比较多,如果到每个页面去判断比较麻烦,这时候就可以用到路由拦截器。
先来看看Flutter中文网的介绍:
MaterialApp有一个onGenerateRoute属性,它在打开命名路由时可能会被调用,之所以说可能,是因为当调用Navigator.pushNamed(...)打开命名路由时,如果指定的路由名在路由表中已注册,则会调用路由表中的builder函数来生成路由组件;如果路由表中没有注册,才会调用onGenerateRoute来生成路由。
肯定有兄弟懵了,怎么一会儿是注册,一会儿又是没有注册?照着中文网的一拷贝,还报错了,真是太阳狗了,别急,看小生给你慢慢道来。
如果你要用路由拦截器,比如,你引入了A页面,但是你没有在路由表中声明,那么你在跳转到A页面的时候,就会触发这个路由拦截器,明白了吗?兄弟们,你只需要引入对应的页面路径,但是不要声明,就可以触发这个拦截器。
请看下面的示例:
// routers.dart
import 'package:flutter/material.dart';
import '../wedgets/myHome.dart';
import '../wedgets/user/login.dart';
final routes = {
// '/': (context) => MyHomePage(),
'/login': (context) => LoginPage(),
};
Route<dynamic> onGenerateRoute(RouteSettings settings) {
String routeName = settings.name;
print('当前访问路由名:$routeName');
if (routeName == '/') {
return MaterialPageRoute(builder: (context) {
return LoginPage();
});
} else {
return null;
}
}
关于路由的讲解,咱们到此为止,详细的示例代码可以点击这里查看。
如果本文有什么错误或者有问题的朋友可以留言评论,欢迎关注本小生,求赞,求赏~
本文原创,如有转载,请注明出处!
青山不改,绿水长流,咱们下一章再见。