跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Dart大前端

Flutter 底部导航与顶部选项卡实战:状态保持与鸿蒙适配

综述由AI生成移动端 App 常采用底 Tab 加顶 Tab 的双导航架构。本文对比了 Flutter 中 BottomNavigationBar 与 Material 3 NavigationBar 的差异,演示了 TabBar 配合 DefaultTabController 的滑动切换实现。重点解决了页面切换后状态丢失的问题,介绍了 IndexedStack 与 AutomaticKeepAliveClientMixin 两种方案。此外还涵盖了 OpenHarmony 环境下底部导航栏避开系统手势条的安全区域适配技巧。

WenxuanMa发布于 2026/4/5更新于 2026/6/1126 浏览
Flutter 底部导航与顶部选项卡实战:状态保持与鸿蒙适配

Flutter 底部导航与顶部选项卡实战:状态保持与鸿蒙适配

在这里插入图片描述

一个复杂的 App 通常包含多个功能模块。在实际开发中,我们最常遇到的就是底部主导航和顶部分类切换的组合架构。本文将深入讲解 Flutter 中最核心的两种多页切换模式:底部导航 (BottomNavigationBar) 和顶部选项卡 (TabBar)。我们会探讨 Material 3 风格的新组件 NavigationBar,解决页面切换时的状态丢失问题,并适配鸿蒙系统的底部手势条。

底部导航:构建 App 骨架

打开微信、淘宝或抖音,你会发现它们都有一个共同的架构:底部有 4-5 个图标,点击切换不同的主页面;顶部可能还有'关注/推荐/热榜'这样的分类切换。这就是移动端最经典的 '底 Tab + 顶 Tab' 双导航架构。

Flutter 提供了两种主流的底部导航组件。

在这里插入图片描述

经典款:BottomNavigationBar

这是最传统、兼容性最好的组件,适合对旧版本系统有要求的场景。

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: '我的'),
        ],
      ),
    );
  }
}

新潮款:NavigationBar (M3)

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

TabBar 通常用于在同一个主栏目下,切换不同的子分类(如新闻 App 的频道)。它必须配合 TabController 使用。

在这里插入图片描述

DefaultTabController (推荐)

最简单的方法是在父级包裹一个 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

如果你希望所有页面在初始化后一直存在,可以使用 IndexedStack。它会一次性加载所有页面,只是不可见部分不渲染。

注意:这会增加内存消耗,如果页面很重,需谨慎使用。

// 修改 Scaffold body
body: IndexedStack(
  index: _currentIndex,
  children: _pages, // 所有页面都会被保留在 Widget 树中,只是不可见
),

方案二:AutomaticKeepAliveClientMixin

如果你使用的是 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')),
    );
  }
}

OpenHarmony 鸿蒙适配专题

在这里插入图片描述

底部导航栏与手势条

现在的鸿蒙手机(如 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 的骨架,核心就是'一底一顶'。

  1. 底部导航:推荐使用 M3 风格的 NavigationBar,视觉更现代。
  2. 顶部 Tab:使用 DefaultTabController 配合 TabBarView 最省事。
  3. 状态保持:不想每次切换都重加载?请记住 IndexedStack (简单粗暴) 或 AutomaticKeepAliveClientMixin (精细控制)。
  4. 鸿蒙适配:时刻留意底部的安全区域,不要让按钮贴底太近。

接下来的布局挑战在于二维空间。如果我要做一个 Pinterest 那样的瀑布流,或者像相册一样的网格呢?我们将突破线性布局的限制,探索 GridView 网格布局的艺术。

目录

  1. Flutter 底部导航与顶部选项卡实战:状态保持与鸿蒙适配
  2. 底部导航:构建 App 骨架
  3. 经典款:BottomNavigationBar
  4. 新潮款:NavigationBar (M3)
  5. 顶部选项卡:TabBar
  6. DefaultTabController (推荐)
  7. 核心难点:页面状态保持
  8. 方案一:IndexedStack
  9. 方案二:AutomaticKeepAliveClientMixin
  10. OpenHarmony 鸿蒙适配专题
  11. 底部导航栏与手势条
  12. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • ROS2 功能包创建与 CMake 编译链路详解
  • 深入理解 Linux 系统文件 I/O:从 open 到重定向的底层逻辑
  • Android System WebView 内核版本更新方法
  • 前端错误处理最佳实践
  • 基于五条标注数据快速完成快递单信息抽取
  • Python 入门指南:学历并非门槛,零基础也能掌握技术
  • Photoshop 集成 ComfyUI 与 Stable Diffusion 实战指南
  • GitHub Git 推送认证失败?切换 SSH 密钥解决密码验证问题
  • Kotlin 异常处理核心:Try 表达式、Nothing 类型与 Java 交互
  • AI 智能体 Skills 驱动开发:从使用到实战
  • C++ STL 哈希表原理与模拟实现
  • 电科金仓发布融合数据库 KES V9,探索 AI 时代新形态
  • 点云预测作为 4D 占用预测代理(一)
  • 数据结构:八种常见排序算法详解
  • 2025 团体程序设计天梯赛 L1-L2 题解(C++)
  • GitHub Copilot 接入 Figma MCP 还原设计稿生成前端代码
  • VSCode AI Copilot 代码补全优化配置指南
  • 企业人才管理中的两种典型现象
  • SQL 基础与进阶:增删改查、查询优化及约束
  • 昇腾 A2 部署 Pi0 机器人大模型:CANN 环境实测

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online