01. Navigator 和路由基础
Flutter 用 Navigator 管理页面栈,就像一摞卡片,push 在顶上放一张,pop 把顶上那张拿走。MaterialPageRoute 定义页面切换动画。Navigator.push 跳转新页面,Navigator.pop 返回上一页。这是命令式导航,简单直接。
dart
// 跳转到第二页
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
// 返回上一页
Navigator.pop(context);
// 带结果返回
// 第二页中:
Navigator.pop(context, '带回去的数据');
// 第一页接收:
final result = await Navigator.push<String>(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
print('返回的结果: $result');Navigator 是从上往下叠的,push 加一页,pop 拿掉一页,就像浏览器的前进后退。
02. 命名路由
MaterialApp 的 routes 属性定义命名路由表,类似 Web 的 URL 路由。Navigator.pushNamed 按名字跳转,路由在 app 入口统一管理。适合中小型项目路径不复杂的场景。onGenerateRoute 处理未知路由或带参数的动态路由。
dart
void main() {
runApp(MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/profile': (context) => ProfilePage(),
'/settings': (context) => SettingsPage(),
},
onGenerateRoute: (settings) {
if (settings.name == '/user') {
final id = settings.arguments as int;
return MaterialPageRoute(
builder: (context) => UserPage(id: id),
);
}
return null;
},
));
}
// 跳转
Navigator.pushNamed(context, '/profile');
Navigator.pushNamed(context, '/user', arguments: 123);命名路由不支持路径参数解析(如 /user/123),需要自己处理。复杂路由用 GoRouter。
03. GoRouter 声明式路由
GoRouter 是 Flutter 官方推荐的声明式路由方案,支持路径参数(/user/:id)、路由守卫(重定向)、嵌套路由、Deep Link 等高级特性。GoRouter 基于 Router 组件,配合 MaterialApp.router 使用。路由定义是一个集中的列表,可读性极强。
dart
import 'package:go_router/go_router.dart';
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomePage(),
),
GoRoute(
path: '/user/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return UserPage(id: int.parse(id));
},
),
// 嵌套路由
ShellRoute(
builder: (context, state, child) => ScaffoldWithNavBar(child: child),
routes: [
GoRoute(path: '/dashboard', builder: (_, __) => DashboardPage()),
GoRoute(path: '/notifications', builder: (_, __) => NotificationPage()),
],
),
],
// 导航守卫
redirect: (context, state) {
final isLoggedIn = AuthService.isLoggedIn;
final isAuthRoute = state.matchedLocation == '/login';
if (!isLoggedIn && !isAuthRoute) return '/login';
if (isLoggedIn && isAuthRoute) return '/';
return null;
},
);
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
);
}
}GoRouter 支持 Deep Link,用户点击 https://yourapp.com/user/123 能直接打开对应页面。
04. 页面传参和接收结果
页面之间传参数有多种方式:构造函数直接传(最直接)、命名路由的 arguments 传递、用状态管理框架全局共享。接收返回结果用 await Navigator.push,第二页 pop 时塞数据第一页就能收到。页面想阻止返回(比如有未保存的修改)用 WillPopScope 拦截。
dart
// 方式1: 构造函数传参
class DetailPage extends StatelessWidget {
final int itemId;
DetailPage({required this.itemId});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text('详情: $itemId')),
);
}
}
// 方式2: 接收返回结果
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (_) => EditPage()),
);
if (result == 'saved') {
// 刷新列表
}
},
child: Text('编辑'),
);
}
// 方式3: 防止误返回
WillPopScope(
onWillPop: () async {
if (_hasUnsavedChanges) {
final shouldLeave = await showDialog<bool>(
context: context,
builder: (_) => AlertDialog(
title: Text('确定离开?'),
content: Text('有未保存的修改'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, true), child: Text('离开')),
TextButton(onPressed: () => Navigator.pop(context, false), child: Text('留下')),
],
),
);
return shouldLeave ?? false;
}
return true;
},
child: ...\n)WillPopScope 包装页面可以监听系统的返回按钮(Android)或侧滑返回(iOS),做拦截。
05. 底部导航和 Tab 切换
底部导航栏是 App 最常见的导航方式。BottomNavigationBar 组件配合 IndexedStack 保持各 Tab 页面状态(切换 Tab 不销毁页面)。Scaffold 的 bottomNavigationBar 属性放导航栏,body 是当前显示的页面。配合 PageView 还能实现滑动手势切换。
dart
class MainScreen extends StatefulWidget {
@override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _currentIndex = 0;
final _pages = [
HomePage(),
DiscoverPage(),
ProfilePage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) => setState(() => _currentIndex = index),
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
BottomNavigationBarItem(icon: Icon(Icons.explore), label: '发现'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
],
),
);
}
}IndexedStack 缓存所有子页面,切换 Tab 时页面状态保持。如果不需要缓存直接 _pages[_currentIndex] 即可。
知识测验
第 1/4 题正确 0
Flutter Navigator.push 的作用?
下一节
下一节 网络请求