Flutter 底部导航与顶部选项卡多页切换实战
讲解 Flutter 中 BottomNavigationBar、NavigationBar 及 TabBar 的使用,涵盖 Material 3 风格组件、页面滑动切换及状态保持方案(IndexedStack、AutomaticKeepAliveClientMixin),并包含 OpenHarmony 底部手势条的安全区域适配指南。

讲解 Flutter 中 BottomNavigationBar、NavigationBar 及 TabBar 的使用,涵盖 Material 3 风格组件、页面滑动切换及状态保持方案(IndexedStack、AutomaticKeepAliveClientMixin),并包含 OpenHarmony 底部手势条的安全区域适配指南。


打开你手机里的微信、淘宝或抖音,你会发现它们都有一个共同的架构:底部有 4-5 个图标,点击切换不同的主页面;顶部可能还有'关注/推荐/热榜'这样的分类切换。
这就是移动端最经典的 '底 Tab + 顶 Tab' 双导航架构。
本文你将学到:
BottomNavigationBar (经典) 与 NavigationBar (Material 3) 的区别TabBar + TabBarView 实现滑动切换Flutter 提供了两种主流的底部导航组件。

这是最传统、兼容性最好的组件。
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int _currentIndex = 0;
final List<Widget> _pages = [
const HomePage(),
const CategoryPage(),
const ProfilePage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_currentIndex], // 根据下标显示对应页面
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) => setState(() => _currentIndex = index),
type: BottomNavigationBarType.fixed, // 超过 3 个 item 必须设置 fixed
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
BottomNavigationBarItem(icon: Icon(Icons.category), label: '发现'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
],
),
);
}
}
Material 3 引入了更高、更圆润的 NavigationBar。它自带点击涟漪和胶囊状的指示器,视觉效果更好。
NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) => setState(() => _currentIndex = index),
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: '首页',
),
NavigationDestination(
icon: Icon(Icons.explore_outlined),
selectedIcon: Icon(Icons.explore),
label: '发现',
),
// ...
],
)
TabBar 通常用于在同一个主栏目下,切换不同的子分类(如新闻 App 的频道)。它必须配合 TabController 使用。

最简单的方法是在父级包裹一个 DefaultTabController,这样我们就不用手动管理 Controller 了。
class NewsPage extends StatelessWidget {
const NewsPage({super.key});
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3, // Tab 数量
child: Scaffold(
appBar: AppBar(
title: const Text('资讯'),
bottom: const TabBar(
tabs: [
Tab(text: '推荐'),
Tab(text: '科技'),
Tab(text: '体育'),
],
),
),
body: const TabBarView(
children: [
Center(child: Text('推荐内容列表')),
Center(child: Text('科技内容列表')),
Center(child: Text('体育内容列表')),
],
),
),
);
}
}

默认情况下,当你从'首页'切换到'我的',再切回'首页'时,首页会被重建(列表滚动位置丢失,输入框清空)。这是因为 Scaffold.body 直接替换了 Widget。
如果你希望所有页面在初始化后一直存在,可以使用 IndexedStack。它会一次性加载所有页面(注意内存消耗)。
// 修改 Scaffold body
body: IndexedStack(
index: _currentIndex,
children: _pages, // 所有页面都会被保留在 Widget 树中,只是不可见
),
如果你使用的是 PageView 或 TabBarView,更推荐让子页面自己决定是否保持状态。
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
// 1. 混入 AutomaticKeepAliveClientMixin
class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin {
// 2. 重写 wantKeepAlive 返回 true
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context); // 3. 必须调用 super.build
return ListView.builder(
itemCount: 100,
itemBuilder: (c, i) => ListTile(title: Text('Item $i')),
);
}
}

现在的鸿蒙手机(如 Mate 60)默认开启全面屏手势,底部有一条'黑条'或'白条' (Home Indicator)。
如果你的 BottomNavigationBar 高度写死,或者没有适配 SafeArea,底部的图标可能会被这个手势条遮挡。
Flutter 的 Scaffold + BottomNavigationBar 默认已经处理了 SafeArea。但如果你使用了自定义的底部栏(比如 Stack 里的 Positioned),务必包裹 SafeArea 并设置 bottom: true。
Align(
alignment: Alignment.bottomCenter,
child: SafeArea(
child: Container(
height: 60,
color: Colors.white,
child: Row(...),
),
),
)
搭建一个 App 的骨架,核心就是'一底一顶'。
NavigationBar,视觉更现代。DefaultTabController 配合 TabBarView 最省事。IndexedStack (简单粗暴) 或 AutomaticKeepAliveClientMixin (精细控制)。
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online