前言
上一篇文章中,我们用 Sliver 构建了可折叠导航栏、搜索框和按字母分组的联系人列表。但在小屏幕上,点击一个分组还没法跳转到联系人列表------因为我们还没实现导航。
今天这篇文章基于官方教程的「Stack Based Navigation」章节,也是整个 Flutter 入门系列的最后一课 。我们将学习 Flutter 中最基本的导航方式------基于栈的导航(Navigator.push / pop),并实现两种不同屏幕尺寸下的自适应导航模式。
一、什么是基于栈的导航?
"栈"(Stack)是一种后进先出的数据结构,就像一摞盘子------最后放上去的盘子最先被拿走。
Flutter 的导航系统也是这样工作的:

- push:把新页面"压"到栈顶,覆盖当前页面
- pop:把栈顶页面"弹出",回到下面的页面
二、实现小屏幕导航
2.1 Navigator.push 的用法
更新 ContactGroupsPage,点击分组时跳转到联系人列表页:
scala
class ContactGroupsPage extends StatelessWidget {
const ContactGroupsPage({super.key});
@override
Widget build(BuildContext context) {
return _ContactGroupsView(
// 当用户点击某个分组时触发的回调
onListSelected: (list) => Navigator.of(context).push(
// CupertinoPageRoute 创建 iOS 风格的页面跳转
CupertinoPageRoute(
// title:新页面导航栏中显示的标题
title: list.title,
// builder:构建新页面的内容
builder: (context) => ContactListsPage(listId: list.id),
),
),
);
}
}
这段代码包含了导航的三个核心要素:
Navigator.of(context) :从 Widget 树中获取最近的 Navigator。Flutter 的每个应用都有一个内置的 Navigator,你不需要手动创建。
.push() :把新路由压入导航栈,新页面从右侧滑入覆盖当前页面。
CupertinoPageRoute:iOS 风格的路由,自带以下特性:
- 从右向左的滑入动画
- 自动生成返回按钮
- 支持右滑手势返回
- 正确处理标题显示
2.2 返回上一页
返回上一页(pop)不需要你写代码------CupertinoPageRoute 自动在导航栏添加了返回按钮,用户也可以从左边缘右滑返回。如果你需要在代码中主动返回,可以调用:
scss
// 手动返回上一页
Navigator.of(context).pop();
三、实现大屏幕侧边栏
大屏幕不需要页面跳转------点击左侧分组直接更新右侧内容。我们创建两个专用组件。
3.1 侧边栏组件
在 contact_groups.dart 底部添加:
scala
// ContactGroupsSidebar:大屏幕专用的侧边栏组件
// 与 ContactGroupsPage 复用同一个 _ContactGroupsView
// 区别:点击分组不跳转页面,而是调用回调更新右侧面板
class ContactGroupsSidebar extends StatelessWidget {
const ContactGroupsSidebar({
super.key,
required this.selectedListId, // 当前选中的分组 ID(用于高亮)
required this.onListSelected, // 点击分组时的回调
});
final int selectedListId;
final Function(int) onListSelected;
@override
Widget build(BuildContext context) {
return _ContactGroupsView(
selectedListId: selectedListId,
// 点击时不导航,而是把分组 ID 传给父组件
// 父组件(AdaptiveLayout)会用 setState 更新右侧面板
onListSelected: (list) => onListSelected(list.id),
);
}
}
3.2 详情面板组件
在 contacts.dart 底部添加:
scala
// ContactListDetail:大屏幕专用的详情面板组件
// 复用 _ContactListView,但隐藏返回按钮
// 因为在大屏模式下,导航由侧边栏控制,不需要返回按钮
class ContactListDetail extends StatelessWidget {
const ContactListDetail({super.key, required this.listId});
final int listId;
@override
Widget build(BuildContext context) {
return _ContactListView(
listId: listId,
// false = 不自动显示返回按钮
// 大屏模式下用户通过点击侧边栏切换内容,不需要"返回"
automaticallyImplyLeading: false,
);
}
}
四、连接到自适应布局
更新 adaptive_layout.dart,将占位文字替换为真正的侧边栏和详情面板:
scala
import 'package:flutter/cupertino.dart';
import 'package:rolodex/screens/contact_groups.dart';
import 'package:rolodex/screens/contacts.dart';
const largeScreenMinWidth = 600;
class AdaptiveLayout extends StatefulWidget {
const AdaptiveLayout({super.key});
@override
State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}
class _AdaptiveLayoutState extends State<AdaptiveLayout> {
// 当前选中的分组 ID
int selectedListId = 0;
// 侧边栏点击分组时调用
void _onContactListSelected(int listId) {
setState(() {
selectedListId = listId;
});
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;
if (isLargeScreen) {
return _buildLargeScreenLayout();
} else {
// 小屏幕:显示分组列表
// 点击分组 → Navigator.push → 联系人列表页
return const ContactGroupsPage();
}
},
);
}
// 大屏幕布局:侧边栏 + 详情面板
Widget _buildLargeScreenLayout() {
return CupertinoPageScaffold(
backgroundColor: CupertinoColors.extraLightBackgroundGray,
child: SafeArea(
child: Row(
children: [
// 左侧:侧边栏(固定 320px)
SizedBox(
width: 320,
// 使用侧边栏组件(点击分组不导航,而是更新 selectedListId)
child: ContactGroupsSidebar(
selectedListId: selectedListId,
onListSelected: _onContactListSelected,
),
),
// 分隔线
Container(width: 1, color: CupertinoColors.separator),
// 右侧:详情面板(占满剩余空间)
Expanded(
// 根据 selectedListId 显示对应分组的联系人
child: ContactListDetail(listId: selectedListId),
),
],
),
),
);
}
}
五、两种导航模式对比
| 特性 | 小屏幕(手机) | 大屏幕(平板/电脑) |
|---|---|---|
| 导航方式 | Navigator.push(栈导航) | setState 更新右侧面板 |
| 页面切换 | 新页面从右滑入覆盖 | 右侧面板内容直接替换 |
| 返回方式 | 返回按钮 / 右滑手势 | 点击侧边栏其他分组 |
| 导航栈 | 有(push/pop) | 无(不涉及导航栈) |
| 路由组件 | CupertinoPageRoute | 不使用 |
| 代码复用 | _ContactGroupsView |
_ContactGroupsView(同一个!) |
核心思想:底层视图组件是复用的 (_ContactGroupsView 和 _ContactListView),只是外层包装不同------小屏用 ContactGroupsPage(导航式),大屏用 ContactGroupsSidebar(回调式)。
六、整个系列回顾
恭喜你!16 课全部完成,让我们回顾整个学习路径:
| 阶段 | 课程 | 项目 | 核心收获 |
|---|---|---|---|
| Flutter UI 101 | 1-8 | Birdle 猜词游戏 | Widget 基础、布局、StatefulWidget、动画 |
| 状态管理 | 9-12 | 维基百科阅读器 | HTTP 请求、MVVM、ChangeNotifier、ListenableBuilder |
| Flutter UI 102 | 13-16 | Rolodex 通讯录 | Cupertino 风格、自适应布局、Sliver 滚动、导航 |
从零开始,你已经构建了三个完整的 Flutter 应用,掌握了从基础到进阶的核心技能。接下来可以尝试构建自己的应用,或者继续深入学习 Flutter 的动画、测试、发布等主题。
加油,Flutter 开发者!