Navigator 路由
一句话理解
Flutter 的页面管理就像浏览器的标签页——你打开一个页面就是"压栈"(push),返回上一页就是"出栈"(pop)。
页面栈是什么?
想象一摞书:
┌─────────────┐
│ 详情页(最上面,正在看) │ ← 栈顶
├─────────────┤
│ 列表页 │
├─────────────┤
│ 首页(最先打开的) │ ← 栈底
└─────────────┘- push:往这摞书上面再放一本(打开新页面)
- pop:拿走最上面那本(返回上一页)
最基本的跳转:push
点击按钮跳转到新页面:
import 'package:flutter/material.dart';
class NavigatorPushExample extends StatelessWidget {
const NavigatorPushExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('首页')),
body: Center(
child: ElevatedButton(
onPressed: () {
// ─── ★ Navigator.push 跳转 ──────────────
Navigator.push(
context,
// ─── ★ MaterialPageRoute ──────────────
MaterialPageRoute(builder: (context) => const DetailPage()),
// ─── ☆ MaterialPageRoute ──────────────
);
// ─── ☆ Navigator.push 跳转 ──────────────
},
child: const Text('查看详情'),
),
),
);
}
}
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('详情页')),
body: const Center(child: Text('这是详情页内容')),
);
}
}
MaterialPageRoute是最常用的路由,它自带从右往左滑入的过渡动画。
最基本的返回:pop
在详情页的返回按钮:
import 'package:flutter/material.dart';
class DetailWithBackPage extends StatelessWidget {
const DetailWithBackPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
title: const Text('详情页'),
),
body: const Center(child: Text('点击左上角返回')),
);
}
}
pop()不需要指定返回到哪个页面——它永远是回到上一页。
常用操作速查
| 你想做什么 | 代码 | 效果 |
|---|---|---|
| 打开新页面 | Navigator.push(...) | 新页面压入栈顶,可返回 |
| 返回上一页 | Navigator.pop(context) | 弹出当前页面 |
| 替换当前页面 | Navigator.pushReplacement(...) | 替换当前页面,不能返回到旧页面 |
| 清空所有页面再跳转 | Navigator.pushAndRemoveUntil(..., (route) => false) | 清空整个栈,适合"登录后跳首页" |
替换当前页面:pushReplacement
场景:从启动页跳转到首页——用户不应该能返回启动页。
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
);清空栈再跳转:pushAndRemoveUntil
场景:登录成功后跳转首页——用户不应该能返回登录页。
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
(route) => false, // false = 清除所有旧页面
);如果想保留首页在栈底,改为
ModalRoute.withName('/')。
命名路由
上面用的 MaterialPageRoute 叫直接路由——每次跳转都要写完整的页面组件。命名路由则是给每个页面起一个名字(如 /detail),然后用名字跳转。
为什么需要命名路由?
// ❌ 直接路由:跳转代码和页面组件耦合在一起
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DetailPage()),
);
// ✅ 命名路由:只写名字,不管页面组件是什么
Navigator.pushNamed(context, '/detail');好处:跳转代码不用 import 页面组件,所有路由集中管理在一个地方。
注册路由表
在 MaterialApp 中统一注册:
MaterialApp(
initialRoute: '/', // 启动时显示的页面
routes: {
'/': (context) => const HomePage(),
'/detail': (context) => const DetailPage(),
'/settings': (context) => const SettingsPage(),
},
)
routes是一个 Map,key 是路由名字,value 是返回页面的函数。
用名字跳转
// 普通跳转
Navigator.pushNamed(context, '/detail');
// 替换跳转(不能返回)
Navigator.pushReplacementNamed(context, '/home');
// 清空栈再跳转
Navigator.pushNamedAndRemoveUntil(context, '/', (route) => false);更好的方式:onGenerateRoute
onGenerateRoute 让你在跳转前手动处理路由逻辑,可以安全地传递参数:
MaterialApp(
initialRoute: '/',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => const HomePage());
case '/detail':
final id = settings.arguments as int;
return MaterialPageRoute(
builder: (_) => DetailPage(id: id),
);
case '/settings':
return MaterialPageRoute(builder: (_) => const SettingsPage());
default:
return MaterialPageRoute(builder: (_) => const NotFoundPage());
}
},
)
onGenerateRoute和routes不能同时使用。用了onGenerateRoute就删掉routes。
命名路由的局限性
| 问题 | 说明 |
|---|---|
| 不能自定义过渡动画 | 所有页面只能用相同的 MaterialPageRoute |
| 不支持 Web 浏览器前进按钮 | Web 端体验不好 |
| 深度链接难处理 | 从外部链接打开指定页面不方便 |
arguments 不安全 | 类型是 Object?,容易写错 |
建议
对于简单应用可以使用命名路由。有复杂导航需求的应用,推荐使用 go_router。
页面传参
页面之间传递数据,主要有以下几种方式:
方式一:构造函数传参(⭐ 最常用)
这是最简单、最安全的方式——直接在创建页面时把数据传进去。
定义页面:
class DetailPage extends StatelessWidget {
final int id;
final String title;
const DetailPage({
super.key,
required this.id,
required this.title,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(child: Text('ID: $id')),
);
}
}跳转时传参:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailPage(
id: 42,
title: '商品详情',
),
),
);为什么推荐? 类型安全,编译器会帮你检查参数是否传对了。
方式二:pop 返回数据(⭐ 最常用)
场景:打开一个选择页,用户选完后把结果带回来。
发起页面——等待结果:
final result = await Navigator.push<String>(
context,
MaterialPageRoute(builder: (_) => const SelectPage()),
);
if (result != null) {
setState(() => _selected = result);
}选择页面——带数据返回:
ListTile(
title: const Text('苹果'),
onTap: () => Navigator.pop(context, '苹果'), // 第二个参数就是返回值
)关键:发起页面用
await等,选择页面用pop(context, 数据)返回。
方式三:arguments 传参(命名路由专用)
如果你用了命名路由,不能直接用构造函数传参,需要通过 arguments 传递:
发送:
Navigator.pushNamed(
context,
'/detail',
arguments: {'id': 42, 'name': 'Tom'},
);接收:
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
final id = args['id']; // 42
final name = args['name']; // Tom缺点:类型不安全,字段名写错了编译器不会报错。
方式四:全局状态(Provider / Riverpod)
适合多个页面共享的数据(如用户登录信息、购物车):
// 写入
context.read<UserModel>().login(newUser);
// 任意页面读取
final user = context.watch<UserModel>().user;传参方式选择
| 你的场景 | 用哪个 |
|---|---|
| A 页面给 B 页面传简单数据 | 构造函数(方式一) |
| B 页面选完东西返回给 A 页面 | pop 返回值(方式二) |
| 用了命名路由,需要传参 | arguments(方式三) |
| 多个页面共享同一份数据 | 全局状态(方式四) |
iOS 风格的过渡动画
如果你想让页面从底部滑入(iOS 风格),用 CupertinoPageRoute:
Navigator.push(
context,
CupertinoPageRoute(builder: (context) => const DetailPage()),
);完整示例
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: HomePage());
}
}
// ---- 首页:传参过去 + 接收返回 ----
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String _city = '未选择';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('选择城市')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('当前城市: $_city', style: const TextStyle(fontSize: 24)),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
// 构造函数传参(传默认城市)+ pop 返回值
final result = await Navigator.push<String>(
context,
MaterialPageRoute(
builder: (_) => const CityPage(defaultCity: '北京'),
),
);
if (result != null) {
setState(() => _city = result);
}
},
child: const Text('选择城市'),
),
],
),
),
);
}
}
// ---- 城市选择页 ----
class CityPage extends StatelessWidget {
final String defaultCity;
const CityPage({super.key, required this.defaultCity});
@override
Widget build(BuildContext context) {
final cities = ['北京', '上海', '广州', '深圳'];
return Scaffold(
appBar: AppBar(title: Text('选择城市(默认: $defaultCity)')),
body: ListView(
children: cities.map((city) {
return ListTile(
title: Text(city),
onTap: () => Navigator.pop(context, city), // pop 返回数据
);
}).toList(),
),
);
}
}小结
| 方法 | 作用 | 能否返回 |
|---|---|---|
push | 打开新页面 | ✅ 能 |
pop | 返回上一页 | — |
pushReplacement | 替换当前页面 | ❌ 不能 |
pushAndRemoveUntil | 清空栈再跳转 | ❌ 不能 |
pushNamed | 命名路由跳转 | ✅ 能 |
下一步
- go_router 路由——官方推荐的新路由方案,传参更优雅
