老孟导读: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 呢? 答案肯定是不可以的,

  1. 试想如下场景,进入购物App,展示购物列表,点击其中一个进入商品详细页面,使用 push 再次进入购物列表,然后在进入商品详细页面...,如此反复,路由堆栈中将会存放大量的购物列表和商品详细页面的路由,点击返回按钮,会将反复显示购物列表和商品详细页面。
  2. 页面切换时路由动画 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 实战】路由堆栈详解的更多相关文章

  1. SVN与TortoiseSVN实战:补丁详解

    硬广:<SVN与TortoiseSVN实战>系列已经写了五篇,第二篇<SVN与TortoiseSVN实战:标签与分支>和第三篇<SVN与TortoiseSVN实战:Tor ...

  2. SVN与TortoiseSVN实战:补丁详解(转)

    硬广:<SVN与TortoiseSVN实战>系列已经写了五篇,第二篇<SVN与TortoiseSVN实战:标签与分支>和第三篇<SVN与TortoiseSVN实战:Tor ...

  3. Admin注册和路由分发详解

    Admin注册和路由分发详解 1.启动 #autodiscover_modules('admin', register_to=site) 2.注册 1.单例对象 admin.site = AdminS ...

  4. “全栈2019”Java多线程第十四章:线程与堆栈详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  5. BI之SSAS完整实战教程5 -- 详解多维数据集结构

    之前简单介绍过多维数据集(Cube)的结构. 原来计划将Cube结构这部分内容打散,在实验中穿插讲解, 考虑到结构之间不同的部分都有联系,如果打散了将反而不好理解,还是直接一次性全部讲完. 本篇我们将 ...

  6. SPA路由机制详解(看不懂不要钱~~)

    前言 总所周知,随着前端应用的业务功能起来越复杂,用户对于使用体验的要求越来越高,单面(SPA)成为前端应用的主流形式.而大型单页应用最显著特点之一就是采用的前端路由跳转子页面系统,通过改变页面的UR ...

  7. Flutter 2.2 更新详解

    Flutter 2.2 版已正式发布!要获取新版本,您只需切换到 stable 渠道并更新目前安装的 Flutter,或前往 flutter.cn/docs/get-started 从头开始安装. 虽 ...

  8. Windows-007-进程相关命令(netstat、tasklist、taskkill、tskill)实战实例图文详解

    本节主要讲述 Windows 系统下,nestat.tasklist.tskill 三个 CMD 命令的参数,及使用方法:以及如何利用三者结合查看进程信息和结束进程.敬请亲们参阅,希望能对亲们有所帮助 ...

  9. Flutter之MaterialApp使用详解

    来自: https://cloud.tencent.com/developer/article/1337184 字段 类型 navigatorKey(导航键) GlobalKey<Navigat ...

随机推荐

  1. 在eclipse中搜索 datasource.xml 文件:

  2. 聊聊MySQL主从复制的几种复制方式

    目录 异步复制 多线程复制 增强半同步复制 异步复制 MySQL的复制默认是异步的,主从复制至少需要两个MYSQL服务,这些MySQL服务可以分布在不同的服务器上,也可以在同一台服务器上. MySQL ...

  3. CVT1100 错误的修复 2009-10-12 11:38

    我们在用VS2005编译MFC工程时极少会出现如下错误: 一,CVTRES : fatal error CVT1100: 重复的资源.type:MANIFEST, name:1, language:0 ...

  4. gpio模拟i2c驱动

    前段时间做项目,需要gpio模拟i2c通信,最后参考了一些资料,然后编写了一个程序.现在发出来,以免以后忘记,也为一些需要的朋友提供参考.不喜勿喷哈. /* 说明:该程序是基于atmel公司的sama ...

  5. Cinder Volume 服务启动流程分析和周期性任务分析

    1.cinder-volume服务的程序入口 #!/usr/bin/python2 # PBR Generated from u'console_scripts' import sys from ci ...

  6. CSS动画实例:3D立方体

    CSS3支持3D转换,与3D转换有关的属性有: transform:向元素应用 2D或3D 转换. transform-origin:改变被转换元素的位置. transform-style:规定被嵌套 ...

  7. Windows10 上的国产锁屏广告?

    不知从什么时候开始,我的笔记本(Windows 10 Home,联想X1)在开机.锁屏时都会显示一些国产的“公益广告”(可惜不能截屏),有时是关于时令节气,有时是一些鸡汤短句,有时节假日则是叫我爱党爱 ...

  8. 看视频常见的 720p、1080p、4k,这些分辨率到底包含了什么

    从早期的420p,到后来的720p,到现在的非1080p不看.视频的清晰度飞快提升,但是在看到色彩越来越丰富清晰度越来越高的画面时,你有关注过他们的到底是怎么做到的么?我们一起来了解一下吧. 想必大家 ...

  9. JavaSE基础--part1

    Java特性和优势 简单.面向对象.可移植性.高性能.分布式.动态性.多线程.安全性.健壮性 Java的三大版本 JavaSE 标准版(应用于桌面程序,控制台开发) JavaME 嵌入式开发(手机,小 ...

  10. vue组件获取和vue-cli的基本了解

    Vue获取组件的一些方法 this.$refs.xxx 给标签绑定ref属性,获取的是当前DOM对象 给组件绑定ref属性,获取的是组件实例对象 this.$parent 获取当前组件的父组件,为一个 ...