【Flutter 实战】路由堆栈详解

老孟导读:Flutter中路由是非常重要的部分,任何一个应用程序都离不开路由管理,此文讲解路由相关方法的使用和路由堆栈的变化。
Flutter 路由管理中有两个非常重要的概念:
- Route:路由是应用程序页面的抽象,对应 Android 中 Activity 和 iOS 中的 ViewController,由 Navigator 管理。
- Navigator:Navigator 是一个组件,管理和维护一个基于堆栈的历史记录,通过 push 和 pop 进行页面的跳转。
push 和 pop
假设现在有2个页面 A 和 B,A中有一个按钮,点击跳转到 B 页面,A 页面代码:
class APage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: RaisedButton(
child: Text('A 页面'),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return BPage();
}));
},
),
);
}
}
B 页面代码:
class BPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: RaisedButton(
child: Text('B 页面'),
onPressed: () {
},
),
),
);
}
}

当应用程序位于A页面时,路由堆栈中只有A,点击按钮跳转到B页面,路由堆栈中有 B 和 A,且 B 处于栈顶。

点击 B 页面的按钮返回到 A 页面,修改 B 页面按钮点击事件:
RaisedButton(
child: Text('B 页面'),
onPressed: () {
Navigator.of(context).pop();
},
)
路由堆栈的变化:

上面案例的效果是从 B 页面跳转到 A 页面,那是否也可以使用 push 方法?修改 B 页面按钮点击事件:
RaisedButton(
child: Text('B 页面'),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return APage();
}));
},
)
从效果上看也可以跳转到 A 页面,路由堆栈:

那是否可以使用 push 代替 pop 呢? 答案肯定是不可以的,
- 试想如下场景,进入购物App,展示购物列表,点击其中一个进入商品详细页面,使用 push 再次进入购物列表,然后在进入商品详细页面...,如此反复,路由堆栈中将会存放大量的购物列表和商品详细页面的路由,点击返回按钮,会将反复显示购物列表和商品详细页面。
- 页面切换时路由动画 push 和 pop 是不同。
maybePop 和 canPop
上面案例如果点击 A 页面按钮直接调用 pop 会如何?
RaisedButton(
child: Text('A 页面'),
onPressed: () {
Navigator.of(context).pop();
},
)
在 A 页面时路由堆栈中只有 A,调用 pop 后,路由堆栈变化:

此时路由堆栈为空,没有可显示的页面,应用程序将会退出或者黑屏,好的用户体验不应如此,此时可以使用 maybePop,maybePop 只在路由堆栈有可弹出路由时才会弹出路由。
上面的案例在 A 页面执行maybePop:
RaisedButton(
child: Text('A 页面'),
onPressed: () {
Navigator.of(context).maybePop();
},
)
点击后不会出现弹出路由,因为当前路由堆栈中只有 A,在 B页面执行maybePop,将会返回到 A 页面。
也可以通过 canPop 判断当前是否可以 pop:
RaisedButton(
child: Text('B 页面'),
onPressed: () {
if(Navigator.of(context).canPop()){
Navigator.of(context).pop();
}
},
)
pushNamed
pushNamed 是命名路由的方式,需要在 MaterialApp 中配置路由名称:
MaterialApp(
title: 'Flutter Demo',
routes: <String, WidgetBuilder>{
'/A': (context) => APage(),
'/B': (context) => BPage(),
},
home: Scaffold(
body: APage(),
),
)
从 A 跳转到 B:
RaisedButton(
child: Text('A 页面'),
onPressed: () {
Navigator.of(context).pushNamed('/B');
},
)
pushReplacementNamed 和 popAndPushNamed
有A、B、C 三个页面,A页面通过 pushNamed 跳转到 B:
RaisedButton(
child: Text('A 页面'),
onPressed: () {
Navigator.of(context).pushNamed('/B');
},
)
B 通过 pushReplacementNamed 跳转到 C:
RaisedButton(
child: Text('B 页面'),
onPressed: () {
Navigator.of(context).pushReplacementNamed('/C');
},
)
点击 C 页面按钮执行 pop:
RaisedButton(
child: Text('C 页面'),
onPressed: () {
if(Navigator.of(context).canPop()){
Navigator.of(context).pop();
}
},
)

点击 C 页面按钮直接返回到了 A 页面,而不是 B 页面,因为 B 页面使用 pushReplacementNamed 跳转,路由堆栈变化:

B 页面跳转到 C 页面,使用 popAndPushNamed:
RaisedButton(
child: Text('B 页面'),
onPressed: () {
Navigator.of(context).popAndPushNamed('/C');
},
)
popAndPushNamed 路由堆栈和 pushReplacementNamed 是一样,唯一的区别就是 popAndPushNamed 有 B 页面退出动画。
popAndPushNamed 和 pushReplacementNamed 使当前页面不在路由堆栈中,所以通过 pop 无法返回此页面。
适用场景:
- 欢迎页面:应用程序打开后首先进入欢迎界面,然后进入首页,进入首页后不应该再进入欢迎界面。
- 登录页面:登录成功后进入相关页面,此时按返回按钮,不应再进入登录页面。
pushNamedAndRemoveUntil
有如下场景,应用程序进入首页,点击登录进入登录页面,然后进入注册页面或者忘记密码页面...,登录成功后进入其他页面,此时不希望返回到登录相关页面,此场景可以使用 pushNamedAndRemoveUntil。
有A、B、C、D 四个页面,A 通过push进入 B 页面,B 通过push进入 C 页面,C 通过 pushNamedAndRemoveUntil 进入 D 页面同时删除路由堆栈中直到 /B 的路由,C 页面代码:
RaisedButton(
child: Text('C 页面'),
onPressed: () {
Navigator.of(context).pushNamedAndRemoveUntil('/D', ModalRoute.withName('/B'));
},
),
D 页面按钮执行 pop:
RaisedButton(
child: Text('D 页面'),
onPressed: () {
Navigator.of(context).pop();
},
)

从 C 页面跳转到 D 页面路由堆栈变化:

Navigator.of(context).pushNamedAndRemoveUntil('/D', ModalRoute.withName('/B'));
表示跳转到 D 页面,同时删除D 到 B 直接所有的路由,如果删除所有路由,只保存 D:
Navigator.of(context).pushNamedAndRemoveUntil('/D', (Route route)=>false);
路由堆栈变化:

popUntil
有如下场景,在入职新公司的时候,需要填写各种信息,这些信息分为不同部分,比如基本信息、工作信息、家庭信息等,这些不同模块在不同页面,填写信息时可以返回上一页,也可以取消,取消返回到首页,此场景可以使用 popUntil,一直 pop 到指定的页面。
有A、B、C、D 四个页面,D 页面通过 popUntil 一直返回到 A 页面,D 页面代码:
RaisedButton(
child: Text('D 页面'),
onPressed: () {
Navigator.of(context).popUntil(ModalRoute.withName('/A'));
},
)

路由堆栈变化:

传递数据
有如下场景,商品列表页面,点击跳转到商品详情页面,商品详情页面需要商品的唯一id或者商品详情数据,有两种方式传递数据:
第一种:通过构造函数方式:
class ProductDetail extends StatelessWidget {
final ProductInfo productInfo;
const ProductDetail({Key key, this.productInfo}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}
跳转代码:
Navigator.of(context).push(MaterialPageRoute(builder: (context){
return ProductDetail(productInfo: productInfo,);
}));
此种方式无法用于命名路由的跳转方式。
第二种:通过命名路由设置参数的方式:
A 页面传递数据,
RaisedButton(
child: Text('A 页面'),
onPressed: () {
Navigator.of(context).pushNamed('/B',arguments: '来自A');
},
)
B 页面通过 ModalRoute.of(context).settings.arguments 接收数据:
RaisedButton(
child: Text('${ModalRoute.of(context).settings.arguments}'),
onPressed: () {
Navigator.of(context).pushNamed('/C');
},
)

返回数据
B 页面返回代码:
RaisedButton(
child: Text('${ModalRoute.of(context).settings.arguments}'),
onPressed: () {
Navigator.of(context).pop('从B返回');
},
)
A 页面接收返回的数据:
class APage extends StatefulWidget {
@override
_APageState createState() => _APageState();
}
class _APageState extends State<APage> {
String _string = 'A 页面';
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: RaisedButton(
child: Text(_string),
onPressed: () async {
var result =
await Navigator.of(context).pushNamed('/B', arguments: '来自A');
setState(() {
_string = result;
});
},
),
),
);
}
}

push 相关方法返回 Future 类型,使用 await 等待返回结果。
交流
交流
老孟Flutter博客(330个控件用法+实战入门系列文章):http://laomengit.com
欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:
![]() |
![]() |
【Flutter 实战】路由堆栈详解的更多相关文章
- SVN与TortoiseSVN实战:补丁详解
硬广:<SVN与TortoiseSVN实战>系列已经写了五篇,第二篇<SVN与TortoiseSVN实战:标签与分支>和第三篇<SVN与TortoiseSVN实战:Tor ...
- SVN与TortoiseSVN实战:补丁详解(转)
硬广:<SVN与TortoiseSVN实战>系列已经写了五篇,第二篇<SVN与TortoiseSVN实战:标签与分支>和第三篇<SVN与TortoiseSVN实战:Tor ...
- Admin注册和路由分发详解
Admin注册和路由分发详解 1.启动 #autodiscover_modules('admin', register_to=site) 2.注册 1.单例对象 admin.site = AdminS ...
- “全栈2019”Java多线程第十四章:线程与堆栈详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- BI之SSAS完整实战教程5 -- 详解多维数据集结构
之前简单介绍过多维数据集(Cube)的结构. 原来计划将Cube结构这部分内容打散,在实验中穿插讲解, 考虑到结构之间不同的部分都有联系,如果打散了将反而不好理解,还是直接一次性全部讲完. 本篇我们将 ...
- SPA路由机制详解(看不懂不要钱~~)
前言 总所周知,随着前端应用的业务功能起来越复杂,用户对于使用体验的要求越来越高,单面(SPA)成为前端应用的主流形式.而大型单页应用最显著特点之一就是采用的前端路由跳转子页面系统,通过改变页面的UR ...
- Flutter 2.2 更新详解
Flutter 2.2 版已正式发布!要获取新版本,您只需切换到 stable 渠道并更新目前安装的 Flutter,或前往 flutter.cn/docs/get-started 从头开始安装. 虽 ...
- Windows-007-进程相关命令(netstat、tasklist、taskkill、tskill)实战实例图文详解
本节主要讲述 Windows 系统下,nestat.tasklist.tskill 三个 CMD 命令的参数,及使用方法:以及如何利用三者结合查看进程信息和结束进程.敬请亲们参阅,希望能对亲们有所帮助 ...
- Flutter之MaterialApp使用详解
来自: https://cloud.tencent.com/developer/article/1337184 字段 类型 navigatorKey(导航键) GlobalKey<Navigat ...
随机推荐
- 封装react antd的form表单组件
form表单在我们日常的开发过程中被使用到的概率还是很大的,比如包含了登录.注册.修改个人信息.新增修改业务数据等的公司内部管理系统.而在使用时这些表单的样式如高度.上下边距.边框.圆角.阴影.高亮等 ...
- hge引擎示例教程cmake项目
hge引擎的示例代码在vs2017不能很好的运行,需要调不少东西,所以我将其重新整理成cmake的项目. 所有示例均在vs2017 msvc 下测试可以正常运行. 由于缺少libhgehelp.a所以 ...
- equals&hashCode
经典规则:如果重写了equals,必须重写hashCode 为什么??? 这个规则考虑的应用场景:如果你的对象可能放入HashMap,HashSet,不重写会出问题 问题场景: new一个对象,重写了 ...
- 2020大厂web前端面试常见问题总结
本篇收录了一些面试中经常会遇到的经典面试题以及自己面试过程中遇到的一些问题.通过对本篇知识的整理以及经验的总结,希望能帮到更多的前端面试者. 1.web前端项目的结构是怎样的?文件有哪些命名规范? 项 ...
- Dataway与SpringBoot集成干掉后台开发
Dataway与SpringBoot集成干掉后台开发 Dataway让SpringBoot不在需要Controller.Service.DAO.Mapper了. 第一步:引入相关依赖 <depe ...
- hbase运行流程图
hbase运行流程图
- 第2章 RDD编程(2.3)
第2章 RDD编程(2.3) 2.3 TransFormation 基本RDD Pair类型RDD (伪集合操作 交.并.补.笛卡尔积都支持) 2.3.1 map(func) 返回一个新的RDD,该 ...
- 牛客网PAT练兵场-打印沙漏
题目地址:https://www.nowcoder.com/pat/6/problem/4053 题意:模拟题 /** * Copyright(c) * All rights reserved. * ...
- [Redhat虚拟机安装][VirtualBox][NET:Registered protocol family 2]
错误情况 今晚试着用VirtualBox虚拟机安装RedHat,但是安装过程中一直卡在一个NET:Registered protocol family 2这个地方. 错误截图 情况如下图所示: 解决方 ...
- 需要分析竞争对手的网站哪些SEO数据
http://www.wocaoseo.com/thread-10-1-1.html 怎样分析竞争对手的网站?在做网站的前我们会对同行的竞争对手进行研究和分析,对判定同行的关键词竞争程度 ...

