背景

训练营活动中有一项要求为实现底部选项卡,这其实也是很普遍的一个功能,现在的GUI应用或者Web应用,选项卡或者说标签页的实现是一个很普通的功能,可能很好的帮助组织界面的功能,避免界面的混乱或者用户体验的不便。

实现方案的比较和推荐

在Flutter中实现底部选项(底部导航)的主要技术方案有:

1. BottomNavigationBar(官方标准方案)

  • Material Design 2 风格

  • 最简单易用

  • 支持3-5个标签项

  • 代码量最少

2. NavigationBar(推荐 - Material 3)

  • Material Design 3 新组件

  • 现代设计风格

  • 更好的动画效果

  • 无项数限制

3. CupertinoTabScaffold(iOS风格)

  • iOS原生外观

  • 使用CupertinoTabBar

  • 适合跨平台应用

4. 自定义BottomAppBar + FloatingActionButton

  • 灵活度最高

  • 可创建特殊设计效果

  • 需要更多代码

5. 第三方包

根据你的pubspec.yaml,建议:

  • persistent_bottom_nav_bar - 功能丰富,动画效果好

  • bottom_navy_bar - 简洁美观

  • salomon_bottom_bar - 交互效果强

  • curved_navigation_bar - 设计独特

建议选择:

  • 快速开发:使用 NavigationBar 或 BottomNavigationBar

  • iOS用户多CupertinoTabScaffold

  • 需要特殊效果persistent_bottom_nav_bar 或自定义方案

作为开发新手,听人劝,吃饱饭,果断选择了推荐的NavigationBar方案。

变更要点

关键变更:

  1. 底部导航栏 - 使用 NavigationBar 组件,4个标签页:

    • 🖼️ 镜像 - 展示现有的 Pipeline 列表(下拉刷新、上拉加载)

    • 📋 任务 - 任务页面(待开发)

    • 📊 统计 - 统计页面(待开发)

    • 👤 我的 - 个人页面(待开发)

  2. 页面分离 - 将原有的 Pipeline 列表逻辑提取到 MirrorPage 类

  3. 状态管理 - 底部导航切换由 _selectedIndex 控制

AI很容易的就将代码给准备好了,基本上没有出现什么问题。

运行验证

鸿蒙模拟器截屏:

感觉还不错吧,图标都是默认自带的。。。

使用定制化图标

因为个人不善于制作图标,也就不准备改为使用定制化的图标了。

不过还是想了解到的方案放在这里以供后面参考。

步骤概览(3 步)

  1. 把自定义图标文件放到工程的 assets/ 目录。
  2. 在 pubspec.yaml 注册这些 assets(并可选添加 flutter_svg 依赖以支持 SVG)。
  3. 在 NavigationDestination 中用 icon / selectedIcon 替换原来的 Icon(...)

示例改动(PNG 位图)

flutter:
  assets:
    - assets/icons/image.png
    - assets/icons/assignment.png
    - assets/icons/bar_chart.png
    - assets/icons/person.png
NavigationDestination(
  icon: ImageIcon(AssetImage('assets/icons/image.png')),
  selectedIcon: ImageIcon(AssetImage('assets/icons/image.png')),
  label: '镜像',
),

如果你想选中/未选中显示不同图片(例如带色差),可以提供不同文件:

NavigationDestination(
  icon: ImageIcon(AssetImage('assets/icons/image_gray.png')),
  selectedIcon: ImageIcon(AssetImage('assets/icons/image_color.png')),
  label: '镜像',
),

示例改动(SVG,需依赖 flutter_svg

flutter_svg: ^2.0.0
  • 注册 svg assets 同上,然后在代码中:
import 'package:flutter_svg/flutter_svg.dart';

NavigationDestination(
  icon: SvgPicture.asset('assets/icons/image.svg', width:24, height:24, color: Colors.grey),
  selectedIcon: SvgPicture.asset('assets/icons/image.svg', width:24, height:24, color: Theme.of(context).colorScheme.primary),
  label: '镜像',
),

其它注意点

  • 如果使用 ImageIcon 并希望改变颜色,确保图标为单色(或使用 PNG 的 alpha 通道),否则颜色覆盖可能不生效。
  • selectedIcon 字段用于显示选中状态图标(Material 3 NavigationDestination 支持)。若你的 Flutter 版本不支持,可在 NavigationBar 的 selectedIndex 处根据索引条件返回不同图标 widget。
  • 添加/修改 assets 后运行:
flutter pub get
flutter run
  • 若图标未显示,请检查 asset 路径拼写和 pubspec.yaml 缩进。

代码主要变更

build方法

整个页面布局变成有appBar, body及bottomNavigationBar三部分组成,body部分负责根据当前选中的标签选项卡显示对应的内容,bottomNavigationBar负责选项卡的显示及事件响应。详细如下:

 class _MyHomePageState extends State<MyHomePage> {
+  int _selectedIndex = 0;
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
+        title: Text(widget.title),
+      ),
+      body: <Widget>[
+        const MirrorPage(),
+        const TaskPage(),
+        const StatisticsPage(),
+        const ProfilePage(),
+      ][_selectedIndex],
+      bottomNavigationBar: NavigationBar(
+        selectedIndex: _selectedIndex,
+        onDestinationSelected: (index) {
+          setState(() {
+            _selectedIndex = index;
+          });
+        },
+        destinations: const [
+          NavigationDestination(
+            icon: Icon(Icons.image),
+            label: '镜像',
+          ),
+          NavigationDestination(
+            icon: Icon(Icons.assignment),
+            label: '任务',
+          ),
+          NavigationDestination(
+            icon: Icon(Icons.bar_chart),
+            label: '统计',
+          ),
+          NavigationDestination(
+            icon: Icon(Icons.person),
+            label: '我的',
+          ),
+        ],
+      ),
+    );
+  }
+}
+

标签页面定义类

MirrorPage

// 镜像页面 - 显示Pipeline列表
class MirrorPage extends StatefulWidget {
  const MirrorPage({super.key});

  @override
  State<MirrorPage> createState() => _MirrorPageState();
}
class _MirrorPageState extends State<MirrorPage> {
//保留原来业务逻辑
....
}

TaskPage/StatisticsPage/ProfilePage

暂时没有具体能实现,仅仅是作为例子,来保证选项卡功能的完整性。

// 任务页面
class TaskPage extends StatelessWidget {
  const TaskPage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.assignment, size: 64, color: Colors.grey),
          SizedBox(height: 16),
          Text('任务页面', style: TextStyle(fontSize: 18)),
          SizedBox(height: 8),
          Text('功能开发中...', style: TextStyle(color: Colors.grey)),
        ],
      ),
    );
  }
}

// 统计页面
class StatisticsPage extends StatelessWidget {
  const StatisticsPage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.bar_chart, size: 64, color: Colors.grey),
          SizedBox(height: 16),
          Text('统计页面', style: TextStyle(fontSize: 18)),
          SizedBox(height: 8),
          Text('功能开发中...', style: TextStyle(color: Colors.grey)),
        ],
      ),
    );
  }
}

// 我的页面
class ProfilePage extends StatelessWidget {
  const ProfilePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.person, size: 64, color: Colors.grey),
          SizedBox(height: 16),
          Text('我的页面', style: TextStyle(fontSize: 18)),
          SizedBox(height: 8),
          Text('功能开发中...', style: TextStyle(color: Colors.grey)),
        ],
      ),
    );
  }
}

总结

1. 因为是比较基本的功能,相对来说默认的支持以及生态上(比如AI)的支持还是比较好的,学习使用起来相对来说还是比较容易的。

2. 虽然选择了NavigationBar方案,说是对选项卡的个数没有限制,不过如果选项卡数太多的话,用户体验也不一定会更好。设想一下如果有10几20几个选项卡的话,在手机端是不是就没有空间去显示业务内容了,或者用户不会发现有那么多的选项卡?

3. 可能存在的其他选项卡方式: 比如顶部,左侧,右侧,在移动端是不是都不如在底部的设计好?在PC端是不是在顶部会好些?平板端呢?没有数据来说明哪一个更好,哪一个差一些,先把疑问留在这里吧。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐