Navigation
Flutter 提供多种导航组件:BottomNavigationBar(底部导航栏)、TabBar(标签栏)、Navigator(页面路由)。
构造函数
BottomNavigationBar
dart
BottomNavigationBar({
Key? key, // 组件标识
required List<BottomNavigationBarItem> items, // 导航项列表(必填,2~5 个)
ValueChanged<int>? onItemSelected, // 选中项变化回调(旧 API)
ValueChanged<int>? onTap, // 点击回调(推荐)
int currentIndex = 0, // 当前选中索引
double? elevation, // 阴影高度
Color? backgroundColor, // 背景色
Color? selectedItemColor, // 选中项颜色(图标+文字)
Color? unselectedItemColor, // 未选中项颜色
IconThemeData? selectedIconTheme, // 选中图标主题
IconThemeData? unselectedIconTheme, // 未选中图标主题
double selectedFontSize = 14, // 选中文字大小
double unselectedFontSize = 12, // 未选中文字大小
TextStyle? selectedLabelStyle, // 选中标签样式
TextStyle? unselectedLabelStyle, // 未选中标签样式
bool showSelectedLabels = true, // 是否显示选中标签
bool showUnselectedLabels = true, // 是否显示未选中标签
BottomNavigationBarType? type, // 导航栏类型
double? iconSize, // 图标大小
double? selectedIconSize, // 选中图标大小
double? mouseCursor, // 鼠标指针
LandscapeLayout? landscapeLayout, // 横屏布局
bool enableFeedback = true, // 触觉反馈
})BottomNavigationBarItem
dart
BottomNavigationBarItem({
required Widget icon, // 图标(必填)
Widget? activeIcon, // 选中状态图标
String? label, // 标签文字
String? tooltip, // 长按提示
Color? backgroundColor, // 背景色(Shifting 模式用)
})NavigationBar(M3 推荐)
dart
NavigationBar({ // M3 底部导航栏
Key? key,
required List<NavigationDestination> destinations, // 导航项(必填)
int selectedIndex = 0, // 当前索引
ValueChanged<int>? onDestinationSelected, // 选中回调
double? elevation, // 阴影高度
Color? backgroundColor, // 背景色
double? height, // 高度
AnimationDuration? animationDuration, // 动画时长
LabelBehavior? labelBehavior, // 标签显示行为
})NavigationDestination(M3)
dart
NavigationDestination({
required Widget icon, // 图标
Widget? selectedIcon, // 选中图标
required String label, // 标签文字(必填)
String? tooltip, // 长按提示
})TabBar
dart
TabBar({
Key? key,
required List<Tab> tabs, // Tab 列表(必填)
TabController? controller, // 控制器
bool isScrollable = false, // 是否可滚动(标签多时设为 true)
EdgeInsetsGeometry? padding, // 内边距
Color? indicatorColor, // 指示器颜色
bool automaticIndicatorColorAdjustment = true, // 自动调整指示器颜色
double? indicatorWeight, // 指示器高度(默认 2)
EdgeInsetsGeometry? indicatorPadding, // 指示器内边距
Decoration? indicator, // 自定义指示器
TabBarIndicatorSize? indicatorSize, // 指示器大小模式
Color? labelColor, // 选中标签颜色
Color? unselectedLabelColor, // 未选中标签颜色
TextStyle? labelStyle, // 选中标签样式
TextStyle? unselectedLabelStyle, // 未选中标签样式
MaterialTapTargetSize? materialTapTargetSize,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
MouseCursor? mouseCursor,
bool? enableFeedback,
ValueChanged<int>? onTap, // 点击回调
double? overlayColor,
})Tab
dart
Tab({
Key? key,
String? text, // 标签文字(与 icon 二选一)
Widget? icon, // 标签图标
EdgeInsetsGeometry? iconMargin, // 图标间距
double? height, // 高度
Widget? child, // 自定义子组件
})TabBarView
dart
TabBarView({
Key? key,
required List<Widget> children, // 页面列表(必填)
TabController? controller, // 控制器
ScrollPhysics? physics, // 滚动物理效果
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
double? viewportFraction = 1.0, // 视口占比
})TabController
dart
TabController({
required int length, // Tab 数量(必填)
TickerProvider? vsync, // Ticker 提供者(必填,通常用 SingleTickerProviderStateMixin)
int initialIndex = 0, // 初始索引
Duration? animationDuration, // 动画时长
})TabController 属性/方法
| 属性/方法 | 类型 | 说明 |
|---|---|---|
index | int | 当前选中索引 |
animation | Animation<double> | 动画对象 |
length | int | Tab 数量 |
animateTo(index) | void | 动画切换到指定 Tab |
offset | double | 拖拽偏移量 |
dispose() | void | 释放资源 |
属性速查
BottomNavigationBar 属性速查
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
items | List<BottomNavigationBarItem> | 必填 | 导航项(2~5 个) |
currentIndex | int | 0 | 当前选中索引 |
onTap | ValueChanged<int>? | null | 点击回调 |
type | BottomNavigationBarType? | 自动 | 导航栏类型 |
selectedItemColor | Color? | 主题色 | 选中颜色 |
unselectedItemColor | Color? | 灰色 | 未选中颜色 |
showSelectedLabels | bool | true | 显示选中标签 |
showUnselectedLabels | bool | true | 显示未选中标签 |
backgroundColor | Color? | 白色 | 背景色 |
elevation | double? | 8 | 阴影高度 |
BottomNavigationBarType 速查
| 值 | 说明 |
|---|---|
BottomNavigationBarType.fixed | 固定模式:所有项等宽,适合 3~4 项 |
BottomNavigationBarType.shifting | 移动模式:选中项更宽,背景色变化,适合 5 项 |
TabBar 属性速查
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
tabs | List<Tab> | 必填 | Tab 列表 |
controller | TabController? | null | 控制器 |
isScrollable | bool | false | 标签多时设为 true |
indicatorColor | Color? | 主题色 | 指示器颜色 |
indicatorWeight | double? | 2 | 指示器高度 |
labelColor | Color? | 主题色 | 选中标签颜色 |
unselectedLabelColor | Color? | 灰色 | 未选中标签颜色 |
onTap | ValueChanged<int>? | null | 点击回调 |
NavigationBar vs BottomNavigationBar 对比
| 特性 | BottomNavigationBar | NavigationBar (M3) |
|---|---|---|
| Material 版本 | Material 2 | Material 3 |
| 最大项数 | 5 | 无限制(建议 3~5) |
| 选中效果 | 颜色变化 | 背景药丸 + 颜色变化 |
| 指示器 | 无 | 药丸形背景 |
| 推荐度 | 旧项目 | ✅ 新项目推荐 |
Navigator 路由速查
基本路由操作
dart
// 跳转新页面
Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage()));
// 返回上一页
Navigator.pop(context);
// 带返回值的跳转
final result = await Navigator.push(context, MaterialPageRoute(builder: (_) => SecondPage()));
Navigator.pop(context, '返回值');
// 替换当前页面
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => LoginPage()));
// 清空栈并跳转(用于退出登录)
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => LoginPage()),
(route) => false, // 清空所有路由
);
// 返回到指定页面
Navigator.popUntil(context, ModalRoute.withName('/home'));
// 返回根页面
Navigator.popUntil(context, (route) => route.isFirst);命名路由
dart
// MaterialApp 中注册路由
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/detail': (context) => DetailPage(),
'/settings': (context) => SettingsPage(),
},
)
// 跳转命名路由
Navigator.pushNamed(context, '/detail');
// 带参数跳转
Navigator.pushNamed(context, '/detail', arguments: {'id': 123});
// 在目标页面获取参数
final args = ModalRoute.of(context)!.settings.arguments as Map;快速示例
底部导航栏
dart
int currentIndex = 0;
Scaffold(
body: IndexedStack( // 保持页面状态
index: currentIndex,
children: [
HomePage(),
SearchPage(),
ProfilePage(),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
onTap: (index) => setState(() => currentIndex = index),
selectedItemColor: Colors.blue,
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: '搜索'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
],
),
)M3 NavigationBar
dart
int currentIndex = 0;
Scaffold(
body: IndexedStack(
index: currentIndex,
children: [HomePage(), SearchPage(), ProfilePage()],
),
bottomNavigationBar: NavigationBar(
selectedIndex: currentIndex,
onDestinationSelected: (index) => setState(() => currentIndex = index),
destinations: [
NavigationDestination(icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home), label: '首页'),
NavigationDestination(icon: Icon(Icons.search), label: '搜索'),
NavigationDestination(icon: Icon(Icons.person_outline), selectedIcon: Icon(Icons.person), label: '我的'),
],
),
)TabBar(使用 DefaultTabController)
dart
DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text('Tab 示例'),
bottom: TabBar(
tabs: [
Tab(text: '最新'),
Tab(text: '热门'),
Tab(text: '推荐'),
],
),
),
body: TabBarView(
children: [
LatestPage(),
HotPage(),
RecommendPage(),
],
),
),
)TabBar(手动控制)
dart
class _MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
late TabController tabController;
@override
void initState() {
super.initState();
tabController = TabController(length: 3, vsync: this);
}
@override
void dispose() {
tabController.dispose(); // 必须释放
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('手动控制 Tab'),
bottom: TabBar(
controller: tabController,
tabs: [Tab(text: 'A'), Tab(text: 'B'), Tab(text: 'C')],
),
),
body: TabBarView(
controller: tabController,
children: [PageA(), PageB(), PageC()],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 编程式切换 Tab
tabController.animateTo((tabController.index + 1) % 3);
},
child: Icon(Icons.swap_horiz),
),
);
}
}页面跳转并接收返回值
dart
// 跳转
final result = await Navigator.push<String>(
context,
MaterialPageRoute(builder: (_) => EditPage()),
);
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('保存成功: $result')),
);
}
// 目标页面返回值
Navigator.pop(context, '已保存');常见错误
1. BottomNavigationBar 超过 5 个项
dart
// ❌ 错误:超过 5 项会报错
BottomNavigationBar(items: [/* 6 个项 */])
// ✅ 修复:使用 NavigationBar 或重新设计
NavigationBar(destinations: [/* 可以超过 5 个 */])2. TabBar 和 TabBarView 的 controller 不一致
dart
// ❌ 错误:TabBar 和 TabBarView 用不同的 controller
TabBar(controller: ctrl1, ...)
TabBarView(controller: ctrl2, ...) // 不联动!
// ✅ 修复:使用同一个 controller,或都用 DefaultTabController3. IndexedStack vs 切换 Widget
dart
// ❌ 问题:直接切换 Widget 会丢失页面状态
body: pages[currentIndex], // 切换时重建
// ✅ 修复:使用 IndexedStack 保持状态
body: IndexedStack(
index: currentIndex,
children: [Page1(), Page2(), Page3()], // 同时存在,只显示一个
)
// 注意:所有页面都会被构建,如果页面很重可能影响性能4. TabController 未 dispose
dart
// ✅ 必须在 dispose 中释放
@override
void dispose() {
tabController.dispose();
super.dispose();
}