Flutter 页面跳转与导航:我常用的 Navigator 写法(含返回值、传参、命名路由)

Flutter 的页面跳转,我一直觉得有点像“叠盘子”:打开新页面就是把盘子放上去,返回就是把最上面那只拿走。
理解了这个“栈”的模型,很多看起来绕的 API 就突然变得顺眼了。

这篇文章我按自己的使用习惯来写:先把最常用的 Navigator.push/pop 讲清楚,再聊命名路由、传参/返回值,最后补几个我踩过的坑。


1)最常用:push / pop(MaterialPageRoute)

从 A 跳到 B

1
2
3
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const DetailPage()),
);

这句干的事很朴素:把 DetailPage 压到导航栈顶。

从 B 返回到 A

1
Navigator.of(context).pop();

通常我会把“返回”放在 AppBar 的 back 上(Flutter 默认就帮你处理了),或者在按钮里手动 pop()


2)传参:把数据带过去(最省心的方式)

我习惯直接在页面构造函数里收参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// A 页面
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => DetailPage(id: item.id, title: item.title),
),
);

// B 页面
class DetailPage extends StatelessWidget {
const DetailPage({super.key, required this.id, required this.title});
final String id;
final String title;

@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: Text(title)),
body: Text('id = $id'),
);
}

这类传参最大的好处是:类型安全,重构起来也舒服。


3)拿返回值:pop 的时候顺手带回去

有些交互很常见:去编辑页改个昵称,回到上一页刷新一下。
我一般用 push 的返回 Future 来接结果:

1
2
3
4
5
6
7
8
9
10
// A 页面
final newName = await Navigator.of(context).push<String>(
MaterialPageRoute(builder: (_) => const EditNamePage()),
);
if (newName != null) {
setState(() => name = newName);
}

// B 页面:保存并返回
Navigator.of(context).pop('小明');

这一套写下来很直观:去哪里拿结果、从哪里把结果带回,都在代码里摆得明明白白。


4)命名路由:适合“入口多、页面多”的项目

当页面数量上来、跳转入口变多(比如通知、深链、多个模块复用同一页),命名路由会更好管理一点。

最简单的 routes 表

1
2
3
4
5
6
7
8
MaterialApp(
routes: {
'/': (_) => const HomePage(),
'/detail': (_) => const DetailPage(),
},
);

Navigator.of(context).pushNamed('/detail');

带参数:我更常用 onGenerateRoute

routes 表在“无参页面”很好用;一旦要传参,我更倾向 onGenerateRoute 统一处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == '/detail') {
final args = settings.arguments as Map<String, dynamic>;
return MaterialPageRoute(
builder: (_) => DetailPage(
id: args['id'] as String,
title: args['title'] as String,
),
);
}
return null;
},
);

Navigator.of(context).pushNamed(
'/detail',
arguments: {'id': item.id, 'title': item.title},
);

这写法谈不上多优雅,但它的优点是:路由入口集中,出了问题很好排查。


5)我常遇到的两个“导航小坑”

坑 A:context 用错了,push 找不到 Navigator

最典型的情况:你在 showDialogBuilder、或者某个局部组件的 context 里 push,结果报错说找不到 Navigator。
我的经验是:用离 Scaffold 更近的 context(比如页面的 context),或者干脆 Navigator.of(context, rootNavigator: true)

坑 B:列表快速点击导致重复 push

如果 item 点击会请求接口 + 进入详情页,连点两下很容易 push 两次。
我一般会做一个简单的“防抖/锁”,或者在进入详情页前先禁用按钮一小段时间。


结尾:什么时候该上路由库?

只用 Flutter 自带的 Navigator,其实能搞定大多数业务。
如果你要做 Web(URL 同步、深链)、或者复杂的嵌套路由,我会建议看看 go_router 这类库——但那已经是另一篇文章了。

Share