第8章 - 页面导航
嗨,朋友!我是长安。
一个完整的 App 通常有多个页面,比如首页、详情页、设置页等。这一章,我们来学习 Flutter 中的页面导航(Navigation),实现页面之间的跳转和传参。
🤔 Flutter 中的路由
在 Flutter 中,每个页面被称为一个路由(Route)。页面导航就是在不同路由之间切换。
Flutter 使用**栈(Stack)**来管理路由:
┌─────────┐
│ 页面C │ ← 栈顶(当前显示的页面)
├─────────┤
│ 页面B │
├─────────┤
│ 页面A │ ← 栈底
└─────────┘
push(页面C) → 页面C 入栈
pop() → 页面C 出栈,显示页面B
🚀 基础导航
1. 跳转到新页面(push)
// 方式一:使用 MaterialPageRoute
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondPage()),
);
// 方式二:简写
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const SecondPage()),
);
2. 返回上一页(pop)
Navigator.pop(context);
// 或
Navigator.of(context).pop();
3. 完整示例
// 首页
class HomePage extends StatelessWidget {
const HomePage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('首页')),
body: Center(
child: ElevatedButton(
onPressed: () {
// 跳转到详情页
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DetailPage()),
);
},
child: const Text('去详情页'),
),
),
);
}
}
// 详情页
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('详情页')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context); // 返回上一页
},
child: const Text('返回'),
),
),
);
}
}
📤 页面间传递数据
1. 跳转时传参
通过构造函数传递数据:
// 定义接收参数的页面
class ProductDetailPage extends StatelessWidget {
final String productId;
final String productName;
const ProductDetailPage({
super.key,
required this.productId,
required this.productName,
});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(productName)),
body: Center(
child: Text('商品ID: $productId'),
),
);
}
}
// 跳转时传参
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailPage(
productId: '12345',
productName: 'Flutter 入门书籍',
),
),
);
2. 返回时带数据
// 页面A:等待返回结果
class PageA extends StatelessWidget {
const PageA({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('页面A')),
body: Center(
child: ElevatedButton(
onPressed: () async {
// 等待页面B返回的结果
final result = await Navigator.push<String>(
context,
MaterialPageRoute(builder: (context) => const PageB()),
);
if (result != null) {
// 使用返回的数据
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('选择了: $result')),
);
}
},
child: const Text('选择选项'),
),
),
);
}
}
// 页面B:返回数据
class PageB extends StatelessWidget {
const PageB({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('页面B')),
body: ListView(
children: [
ListTile(
title: const Text('选项 1'),
onTap: () => Navigator.pop(context, '选项 1'), // 返回数据
),
ListTile(
title: const Text('选项 2'),
onTap: () => Navigator.pop(context, '选项 2'),
),
ListTile(
title: const Text('选项 3'),
onTap: () => Navigator.pop(context, '选项 3'),
),
],
),
);
}
}
🏷️ 命名路由
当应用页面较多时,可以使用命名路由统一管理。
1. 定义路由表
在 MaterialApp 中定义:
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
// 初始路由
initialRoute: '/',
// 路由表
routes: {
'/': (context) => const HomePage(),
'/detail': (context) => const DetailPage(),
'/settings': (context) => const SettingsPage(),
'/profile': (context) => const ProfilePage(),
},
);
}
}
2. 使用命名路由跳转
// 跳转到命名路由
Navigator.pushNamed(context, '/detail');
// 带参数跳转
Navigator.pushNamed(
context,
'/detail',
arguments: {'id': '123', 'title': '商品标题'},
);
3. 获取传递的参数
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
Widget build(BuildContext context) {
// 获取传递的参数
final args = ModalRoute.of(context)!.settings.arguments as Map<String, String>;
return Scaffold(
appBar: AppBar(title: Text(args['title'] ?? '')),
body: Center(
child: Text('ID: ${args['id']}'),
),
);
}
}
🔄 导航的其他操作
1. 替换当前页面
// 用新页面替换当前页面(不能返回)
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const NewPage()),
);
// 命名路由版本
Navigator.pushReplacementNamed(context, '/new');
2. 清空栈并跳转
// 清空所有页面,跳转到新页面(常用于登录后跳转首页)
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
(route) => false, // 移除所有之前的路由
);
// 命名路由版本
Navigator.pushNamedAndRemoveUntil(context, '/home', (route) => false);
3. 返回到指定页面
// 返回到第一个页面
Navigator.popUntil(context, (route) => route.isFirst);
// 返回到指定命名路由
Navigator.popUntil(context, ModalRoute.withName('/home'));
📱 底部导航栏
大多数 App 都有底部导航栏,这是一种特殊的导航模式。
class MainPage extends StatefulWidget {
const MainPage({super.key});
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int _currentIndex = 0;
// 页面列表
final List<Widget> _pages = [
const HomePage(),
const SearchPage(),
const MessagePage(),
const ProfilePage(),
];
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed, // 固定模式
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '首页',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: '搜索',
),
BottomNavigationBarItem(
icon: Icon(Icons.message),
label: '消息',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: '我的',
),
],
),
);
}
}
// 各个页面
class HomePage extends StatelessWidget {
const HomePage({super.key});
Widget build(BuildContext context) {
return const Center(child: Text('首页'));
}
}
class SearchPage extends StatelessWidget {
const SearchPage({super.key});
Widget build(BuildContext context) {
return const Center(child: Text('搜索'));
}
}
class MessagePage extends StatelessWidget {
const MessagePage({super.key});
Widget build(BuildContext context) {
return const Center(child: Text('消息'));
}
}
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
Widget build(BuildContext context) {
return const Center(child: Text('我的'));
}
}
📑 TabBar 导航
顶部 Tab 切换导航:
class TabBarDemo extends StatelessWidget {
const TabBarDemo({super.key});
Widget build(BuildContext context) {
return DefaultTabController(
length: 3, // Tab 数量
child: Scaffold(
appBar: AppBar(
title: const Text('TabBar 示例'),
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car), text: '汽车'),
Tab(icon: Icon(Icons.directions_transit), text: '公交'),
Tab(icon: Icon(Icons.directions_bike), text: '自行车'),
],
),
),
body: const TabBarView(
children: [
Center(child: Text('汽车内容')),
Center(child: Text('公交内容')),
Center(child: Text('自行车内容')),
],
),
),
);
}
}
🎯 页面转场动画
自定义转场动画
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => const DetailPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
// 淡入效果
return FadeTransition(opacity: animation, child: child);
// 或者滑动效果
// return SlideTransition(
// position: Tween<Offset>(
// begin: const Offset(1, 0),
// end: Offset.zero,
// ).animate(animation),
// child: child,
// );
},
transitionDuration: const Duration(milliseconds: 300),
),
);
📝 小结
这一章我们学习了:
- ✅ 路由的基本概念(栈结构)
- ✅ push 和 pop 导航
- ✅ 页面间传递数据(跳转传参、返回带参)
- ✅ 命名路由的使用
- ✅ 导航的其他操作(替换、清空栈)
- ✅ 底部导航栏实现
- ✅ TabBar 导航
- ✅ 自定义转场动画
导航选择指南
| 场景 | 推荐方式 |
|---|---|
| 简单应用 | 基础导航(push/pop) |
| 中型应用 | 命名路由 |
| 主要页面切换 | 底部导航栏 / TabBar |
| 复杂应用 | go_router 等路由库 |
💪 练习题
- 创建一个包含 3 个页面的应用,实现页面之间的跳转和返回
- 实现一个商品列表页,点击商品跳转到详情页,并传递商品信息
- 创建一个带底部导航栏的应用,包含首页、分类、购物车、我的四个页面
点击查看答案
练习2:商品列表和详情
// 商品模型
class Product {
final String id;
final String name;
final double price;
Product({required this.id, required this.name, required this.price});
}
// 商品列表页
class ProductListPage extends StatelessWidget {
final List<Product> products = [
Product(id: '1', name: 'Flutter 实战', price: 99.0),
Product(id: '2', name: 'Dart 入门', price: 59.0),
Product(id: '3', name: '移动开发指南', price: 79.0),
];
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('商品列表')),
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ListTile(
title: Text(product.name),
subtitle: Text('¥${product.price}'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailPage(product: product),
),
);
},
);
},
),
);
}
}
// 商品详情页
class ProductDetailPage extends StatelessWidget {
final Product product;
const ProductDetailPage({super.key, required this.product});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(product.name)),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(product.name, style: const TextStyle(fontSize: 24)),
const SizedBox(height: 10),
Text('¥${product.price}',
style: const TextStyle(fontSize: 20, color: Colors.red)),
const Spacer(),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
child: const Text('加入购物车'),
),
),
],
),
),
);
}
}
🚀 下一步
恭喜你!基础教程全部完成!🎉
现在你已经掌握了 Flutter 的核心知识,是时候用项目实战来巩固所学了。前往 项目实战,我们一起来开发一个完整的 App!
由 编程指南 提供
