前言:
在 Flutter 里,页面跳转其实不是“跳”,而是“压栈 / 出栈” 。如果你只会 Navigator.push,那你只会了 30% 。
需要注意的是:本文基于 Flutter Navigator 1.0 体系,
不涉及 Router / Navigator 2.0。
一、Flutter 的页面跳转本质是什么?
在Android中:
Activity : Activity → 启动 / finish
在 iOS 里: ViewController → push / pop
在Flutter中
- 页面 Widget 是被包装在 Route 中,再由 Navigator 管理
- 页面栈 = Navigator 管理的 Stack
示意图:
push→ 压一个页面到栈顶(跳转到C,就是将C压入栈顶)pop→ 把栈顶页面弹掉(返回到B,就是将C从栈顶弹出)- 永远只显示 栈顶页面
Navigator = 页面栈管理器
二、最基础的页面跳转(必须会)
1、push:跳转到新界面
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailPage(),
),
);
作用:将DetailPage压入页面栈顶,显示在最上方。
2、pop
Navigator.pop(context);
作用:把当前页面从页面栈移除。
三、页面跳转时传值
1、跳转时传值(push → 传参数)
场景:列表 → 详情页
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(
title: 'Flutter 真香',
id: 1001,
),
),
);
DetailPage 接收参数
class DetailPage extends StatelessWidget {
final String title;
final int id;
const DetailPage({
super.key,
required this.title,
required this.id,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Text('文章ID:$id'),
);
}
}
2、返回时带结果(pop → 回传数据)
场景:选择页 → 返回选中的结果
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectPage(),
),
);
// result 就是 pop 传回来的值
print('选择结果:$result');
SelectPage 中返回数据
Navigator.pop(context, '南京');
Navigator 的 push 方法会返回一个 Future,
当页面通过 pop(result) 退出时,该 Future 会完成并携带 result。
四、命令路由(Named Routes)
当页面多了,你一定会遇到这个问题: MaterialPageRoute 写得太多了,看着好乱 。那如何解决这个问题呢?这一章节将给出解决方案:
第一步:定义路由表
MaterialApp(
routes: {
'/': (context) => const HomePage(),//主页
'/detail': (context) => const DetailPage(),//详情页
},
);
第二步:使用命名路由跳转
Navigator.pushNamed(context, '/detail');
第三步:返回
Navigator.pop(context);
注意:命名路由的缺点(必须知道),小项目可以用,大项目慎用
- 参数不直观
- 复杂页面传参容易乱
- 不利于重构
- 编译期无法校验路由是否存在(字符串路由)
五、带参数的命名路由
命名路由同样存在携带参数的需求,请参照本章节
1、使用 arguments
Navigator.pushNamed(
context,
'/detail',
arguments: {
'id': 1001,
'title': 'Flutter 路由',
},
);
2、在目标页面获取
final args = ModalRoute.of(context)!.settings.arguments as Map;
final id = args['id'];
final title = args['title'];
看上去非常美好,但其实存在一些问题,在大项目中偶尔会让开发者崩溃。
部分问题如下:
- 没类型检查
- key 写错不会报错
这就是为什么很多人后面会转 go_router / auto_route
六、pushReplacement / pushAndRemoveUntil(非常重要)
大家是不是经常遇到这样的场景,登录成功跳转到首页后,我们需要移除登录页。那在Flutter中如何解决这个问题?
pushReplacement —— 替换当前页面
场景:登录页 → 首页(不能返回登录页)
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const HomePage()),
);
注意:替换当前页面(只移除最顶层一个),登录成功后必用
pushAndRemoveUntil —— 清空历史栈
场景:账号被踢下线 → 跳转到登录页面,并移除所有页面
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const LoginPage()),
(route) => false,
);
常用于:
- 注册/登录成功后跳转到主界面(或反过来登出后回登录)
- 强制重置导航栈(比如深层页面跳到登录,避免返回后还在已登录状态)
也许你注意到了这段代码(route) => false,如果改为(route) => true会发生什么? 这个参数啥都不移除”(几乎没人这么写,没意义)
下面的表格将列出几种常见的用法
| 代码写法 | 效果 | 典型使用场景 | 用户按返回键会发生什么 |
|---|---|---|---|
Navigator.push(...) | 正常叠加新页面 | 普通页面跳转 | 返回上一页 |
Navigator.pushReplacement(...) | 替换当前页面(只移除最顶层一个) | 从登录页跳转到首页,希望用户无法返回登录页,但仍保留 splash 等更早的页面(如果存在) | 可能返回 splash 或其他 |
Navigator.pushAndRemoveUntil(..., (route) => false) | 跳转 + 清空整个栈 | 登录成功后跳首页、注册完成、登出后回登录页 | 通常会直接退出应用(无历史可回) |
Navigator.pushAndRemoveUntil(..., ModalRoute.withName('/home')) | 跳转 + 移除直到名为 '/home' 的路由为止 | 跳到已存在的首页,清除中间页面 | 返回到 /home 之前的页面 |
最后一种可能比较特殊,我使用AI生成示意图,折腾了半天也不满意,算了,直接用文本表示吧。
执行前:
栈底(最早的页面)
┌───────────────────────┐
│ /splash (根,通常不移除) │
├───────────────────────┤
│ /login │
├───────────────────────┤
│ /home │ ← 这里 name == '/home',关键停止点
├───────────────────────┤
│ /settings │
├───────────────────────┤
│ /profile │ ← 当前页面
└───────────────────────┘
栈顶(当前页面)
执行如下代码:
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const NewPage()),
ModalRoute.withName('/home'),
);
执行后栈变化
- 从栈顶(/profile)开始逐个 pop页面
- pop /profile → pop /settings
- 遇到 /home 时,predicate 返回 true → 停止 pop,保留 /home 及下面所有页面
- 然后 push NewPage 到栈顶
执行结果:
栈底(最早的页面)
┌───────────────────────┐
│ /splash │
├───────────────────────┤
│ /login │
├───────────────────────┤
│ /home │ ← 保留,从这里开始不 pop
├───────────────────────┤
│ NewPage │ ← 新页面(当前可见)
└───────────────────────┘
栈顶(当前页面)
速记口诀
- (route) => false = “全清空”
- (route) => true = “啥都不移除”
- ModalRoute.withName('xxx') = “移除到名为 xxx 的页面为止,xxx 保留”
七、Navigator.of(context) 到底是什么?
工作中,你大概率会碰见如下两种写法:
Navigator.push(context, route);
Navigator.of(context).push(route);
这两种写法本质是一致的,是一个 InheritedWidget of(context) 是从 Widget 树里找到最近的 Navigator
常见的坑:
//返回上一次页面,弹窗确认
showDialog(
context: context,
builder: (context) {
return AlertDialog(
actions: [
TextButton(
onPressed: () {
Navigator.pop(context); // 只会关闭 Dialog
},
child: Text('确定'),
),
],
);
},
);
你会发现,点击“确认”只是关闭了弹窗,并没有预想的返回上一个页面。
原因:
这里的 Navigator.pop(context) 中的 context 是 Dialog的context ,而不是页面的context。因此,它只会关闭当前的Dialog,而不会返回上一页。
关键知识点
- Context作用域 :
- showDialog 中的 context 参数是调用页面的context
- builder 函数中的 context 参数是Dialog的context
- 两者是不同的对象,作用域不同
- Navigator.pop() :
- 总是作用于当前context所在的导航栈
- 对于Dialog来说,showDialog 会通过 Navigator 再 push 一个 DialogRoute,因此 pop(context) 只会弹出该 Route,而不会影响页面 Route。
- 对于页面来说,它在主导航栈中
- 导航栈原理 :
- Flutter使用导航栈来管理页面和弹窗
- 每个页面和弹窗都是栈中的一个元素
- pop() 操作会移除栈顶的元素
修复:
// 在页面中使用
showDialog(
context: context,
builder: (dialogContext) {
return AlertDialog(
title: Text('确认操作'),
content: Text('确定要返回上一页吗?'),
actions: [
TextButton(
onPressed: () {
Navigator.pop(dialogContext); // 关闭Dialog
},
child: Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(dialogContext); // 先关闭Dialog
Navigator.pop(context); // 再返回上一页
},
child: Text('确定'),
),
],
);
},
)
📌 记住:想关闭A,就用A创建时所在层级的 context。
八、实际项目的推荐写法
封装一个跳转工具类:
class AppNavigator {
static push(BuildContext context, Widget page) {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => page),
);
}
static pop(BuildContext context, [result]) {
Navigator.pop(context, result);
}
}
使用时:
AppNavigator.push(context, const DetailPage());
需要注意:⚠️ 简单项目可以这样封装,中大型项目建议封装 Route 或使用路由框架。
九、什么时候该换路由框架?
| 场景 | Navigator | go_router |
|---|---|---|
| Demo / 学习 | ✅ | ❌ |
| 中小项目 | ✅ | 可选 |
| 多 Tab / Web / 深链 | ❌ | ✅ |
| 强类型路由 | ❌ | ✅ |
| 对于我这类新手,我感觉还是先从简单的来,先把Navigator用熟,熟悉后可慢慢切换至go_router |
十、记忆点总结(学习期间总结,帮助快速记忆)
- (页面跳转)push / pop = 压栈 / 出栈
- 页面传值 = 构造函数
- 页面回传 = Future + pop(result)
- 登录后跳首页 = pushReplacement
- 清空历史 = pushAndRemoveUntil
- Navigator 是 Widget 树里的“栈管理器”
写在最后
十一、2026 年视角:Navigator 1.0 还香吗?
写这篇文章时,我特意标注了基于 Navigator 1.0,因为:
- 它简单、直观,上手零成本
- 所有代码在最新 Flutter(3.24+ / 4.x)里都能跑
- 小项目、学习、纯移动端依然是最高效的选择
但如果你计划做:
- 支持 Web(URL 同步、前进后退)
- 深层链接(从推送/分享打开特定页面)
- 复杂嵌套导航(TabBar + Drawer + 子页面栈)
- 类型安全路由(避免字符串拼错)
那建议尽快迁移到 go_router(Flutter 官方推荐路由包)。
go_router 本质是 Navigator 2.0 的“人性化封装”,语法类似命名路由,但支持:
/users/:id路径参数context.go('/home')/context.push('/detail')- redirect(登录校验)
- ShellRoute(带底部导航的壳)
- 强类型(可选 + 代码生成)
后续我会继续写 go_router 系列,敬请期待!
一句话:先把 Navigator 1.0 用熟,再优雅升级到 go_router —— 这才是大多数 Flutter 开发者的学习路径。