Flutter框架跨平台鸿蒙开发——BottomNavigationBar底部导航栏详解

BottomNavigationBar底部导航栏详解


一、BottomNavigationBar组件概述

BottomNavigationBar是Material Design中常用的底部导航组件,它位于应用底部,提供3-5个主要功能的快速访问入口。底部导航栏是移动应用中最常见的导航模式之一,用户可以通过点击不同的图标在不同的页面之间切换。

BottomNavigationBar的设计理念

BottomNavigationBar
快速导航
状态指示
视觉统一
易于访问
主要功能入口
常用页面切换
最少点击
当前页面标识
视觉状态变化
图标+文字
Material规范
固定位置
一致样式
拇指可及区域
大触摸目标
直观交互

底部导航栏的设计遵循Material Design规范,强调可访问性和直观性。它固定在屏幕底部,位于用户拇指最容易到达的区域,可以大大提高操作效率。

二、BottomNavigationBar的主要属性

核心属性详解表

属性名 类型 说明 必需 默认值
items List 导航项列表 是 \[\]
currentIndex int 当前选中项的索引 是 0
onTap ValueChanged 点击回调 否 null
type BottomNavigationBarType 类型 否 BottomNavigationBarType.fixed
iconSize double 图标大小 否 24.0
selectedFontSize double 选中文字大小 否 14.0
unselectedFontSize double 未选中文字大小 否 12.0
selectedIconTheme IconThemeData 选中图标主题 否 null
unselectedIconTheme IconThemeData 未选中图标主题 否 null
backgroundColor Color 背景颜色 否 null
selectedItemColor Color 选中项颜色 否 主题primaryColor
unselectedItemColor Color 未选中项颜色 否 null
showSelectedLabels bool 是否显示选中标签 否 true
showUnselectedLabels bool 是否显示未选中标签 否 true
elevation double 阴影高度 否 8.0
landscapeLayout BottomNavigationBarLandscapeLayout 横屏布局 否 null

BottomNavigationBarItem属性

属性名 类型 说明 必需 默认值
icon Widget 图标组件 是 null
label String 标签文字 是 null
activeIcon Widget 选中时的图标 否 null
tooltip String 提示文字 否 null
backgroundColor Color 背景颜色 否 null

三、基础BottomNavigationBar使用

简单的底部导航栏

dart 复制代码
class BasicBottomNavPage extends StatefulWidget {
  const BasicBottomNavPage({super.key});

  @override
  State<BasicBottomNavPage> createState() => _BasicBottomNavPageState();
}

class _BasicBottomNavPageState extends State<BasicBottomNavPage> {
  int _currentIndex = 0;

  final List<Widget> _pages = [
    _buildPage('首页', Colors.blue, Icons.home),
    _buildPage('发现', Colors.green, Icons.explore),
    _buildPage('消息', Colors.orange, Icons.message),
    _buildPage('我的', Colors.purple, Icons.person),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('底部导航栏基础'),
        backgroundColor: Colors.deepPurple,
        foregroundColor: Colors.white,
      ),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.explore),
            label: '发现',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.message),
            label: '消息',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }

  Widget _buildPage(String title, Color color, IconData icon) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(icon, size: 80, color: color),
          const SizedBox(height: 20),
          Text(
            title,
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 10),
          Text(
            '这是$title页面',
            style: TextStyle(
              fontSize: 16,
              color: Colors.grey[600],
            ),
          ),
        ],
      ),
    );
  }
}

实现要点说明

这个示例展示了BottomNavigationBar的最基本使用方式。关键点包括:

  1. 状态管理 :使用StatefulWidget管理当前选中的索引值_currentIndex
  2. 页面切换:根据当前索引显示对应的页面内容
  3. 点击事件:onTap回调中更新状态,触发UI重新渲染
  4. 类型选择:使用fixed类型,确保所有项目都可见

当用户点击底部导航栏的某个项目时,会触发onTap回调,更新_currentIndex的值,从而显示对应的页面。

四、BottomNavigationBar的两种类型

Fixed类型vs Shifting类型

dart 复制代码
class BottomNavTypesPage extends StatefulWidget {
  const BottomNavTypesPage({super.key});

  @override
  State<BottomNavTypesPage> createState() => _BottomNavTypesPageState();
}

class _BottomNavTypesPageState extends State<BottomNavTypesPage> {
  int _currentIndex = 0;
  BottomNavigationBarType _navType = BottomNavigationBarType.fixed;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('BottomNavigationBar类型'),
        backgroundColor: Colors.indigo,
        foregroundColor: Colors.white,
        actions: [
          SegmentedButton<BottomNavigationBarType>(
            segments: const [
              ButtonSegment(
                value: BottomNavigationBarType.fixed,
                label: Text('Fixed'),
              ),
              ButtonSegment(
                value: BottomNavigationBarType.shifting,
                label: Text('Shifting'),
              ),
            ],
            selected: {_navType},
            onSelectionChanged: (Set<BottomNavigationBarType> newSelection) {
              setState(() {
                _navType = newSelection.first;
              });
            },
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.info, size: 80, color: Colors.indigo),
            const SizedBox(height: 20),
            Text(
              '当前类型: ${_navType == BottomNavigationBarType.fixed ? 'Fixed' : 'Shifting'}',
              style: const TextStyle(fontSize: 20),
            ),
            const SizedBox(height: 10),
            Text(
              _navType == BottomNavigationBarType.fixed
                  ? 'Fixed类型:所有项目固定宽度,显示所有标签'
                  : 'Shifting类型:选中项展开,未选中项只显示图标',
              style: TextStyle(color: Colors.grey[600]),
            ),
          ],
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: _navType,
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
            backgroundColor: Colors.blue,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.explore),
            label: '发现',
            backgroundColor: Colors.green,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.message),
            label: '消息',
            backgroundColor: Colors.orange,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
            backgroundColor: Colors.purple,
          ),
        ],
      ),
    );
  }
}

类型选择指南

Fixed类型:

  • 所有项目固定宽度,始终可见
  • 显示所有项目的标签文字
  • 适合3-5个导航项的场景
  • 每个项目等宽,视觉效果更平衡

Shifting类型:

  • 选中项展开显示图标和标签
  • 未选中项只显示图标
  • 需要3-5个导航项
  • 每个项目可以有不同的背景色
  • 适合需要强调选中状态的场景

选择哪种类型主要取决于设计需求。Fixed类型更传统、更稳定,而Shifting类型更具动感和视觉冲击力。

五、自定义样式和主题

个性化底部导航栏

dart 复制代码
class CustomBottomNavPage extends StatefulWidget {
  const CustomBottomNavPage({super.key});

  @override
  State<CustomBottomNavPage> createState() => _CustomBottomNavPageState();
}

class _CustomBottomNavPageState extends State<CustomBottomNavPage> {
  int _currentIndex = 0;

  final List<Widget> _pages = [
    _buildPage('首页', Colors.teal),
    _buildPage('商城', Colors.amber),
    _buildPage('社区', Colors.red),
    _buildPage('我的', Colors.deepPurple),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('自定义样式'),
        backgroundColor: Colors.teal,
        foregroundColor: Colors.white,
      ),
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        backgroundColor: Colors.white,
        elevation: 0,
        selectedItemColor: Colors.teal,
        unselectedItemColor: Colors.grey,
        selectedFontSize: 14,
        unselectedFontSize: 12,
        iconSize: 28,
        selectedIconTheme: const IconThemeData(
          size: 28,
          color: Colors.teal,
        ),
        unselectedIconTheme: IconThemeData(
          size: 24,
          color: Colors.grey[400],
        ),
        items: [
          BottomNavigationBarItem(
            icon: const Icon(Icons.home_outlined),
            activeIcon: const Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: const Icon(Icons.shopping_bag_outlined),
            activeIcon: const Icon(Icons.shopping_bag),
            label: '商城',
          ),
          BottomNavigationBarItem(
            icon: const Icon(Icons.forum_outlined),
            activeIcon: const Icon(Icons.forum),
            label: '社区',
          ),
          BottomNavigationBarItem(
            icon: const Icon(Icons.person_outline),
            activeIcon: const Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }

  Widget _buildPage(String title, Color color) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            Icons.palette,
            size: 80,
            color: color,
          ),
          const SizedBox(height: 20),
          Text(
            title,
            style: TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
              color: color,
            ),
          ),
          const SizedBox(height: 10),
          const Text('自定义颜色、大小、图标样式'),
        ],
      ),
    );
  }
}

自定义要点

通过自定义主题属性,可以创建符合应用风格的底部导航栏:

  1. 颜色定制:selectedItemColor和unselectedItemColor分别设置选中项和未选中项的颜色
  2. 图标大小:iconSize控制图标的基础大小,配合selectedIconTheme和unselectedIconTheme可以实现选中时图标变大
  3. 字体大小:selectedFontSize和unselectedFontSize分别控制选中项和未选中项的字体大小
  4. 图标切换:使用icon和activeIcon可以在选中时切换不同的图标,比如从轮廓图标切换到实心图标
  5. 去除阴影:将elevation设置为0可以创建无边框的扁平化设计

六、带徽标的底部导航栏

实现徽标提示

dart 复制代码
class BadgeBottomNavPage extends StatefulWidget {
  const BadgeBottomNavPage({super.key});

  @override
  State<BadgeBottomNavPage> createState() => _BadgeBottomNavPageState();
}

class _BadgeBottomNavPageState extends State<BadgeBottomNavPage> {
  int _currentIndex = 0;
  int _messageCount = 5;
  int _cartCount = 3;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('带徽标的底部导航'),
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.notifications, size: 80, color: Colors.red),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _messageCount++;
                });
              },
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red,
                foregroundColor: Colors.white,
              ),
              child: const Text('增加消息数量'),
            ),
            const SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  if (_messageCount > 0) _messageCount--;
                });
              },
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red,
                foregroundColor: Colors.white,
              ),
              child: const Text('清除消息'),
            ),
          ],
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          if (index == 2) {
            setState(() {
              _messageCount = 0;
            });
          }
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        items: [
          const BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          const BottomNavigationBarItem(
            icon: Icon(Icons.shopping_cart),
            label: '购物车',
          ),
          BottomNavigationBarItem(
            icon: Badge(
              label: _messageCount > 99 ? '99+' : _messageCount.toString(),
              child: const Icon(Icons.message),
            ),
            label: '消息',
          ),
          const BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }
}

徽标使用场景

徽标(Badge)是向用户展示未读消息、购物车数量、通知数量等信息的重要方式:

  1. 消息提示:显示未读消息数量,点击后清零
  2. 购物车:显示购物车中的商品数量
  3. 通知:显示待处理的通知数量
  4. 更新提示:使用小红点提示有更新内容

使用徽标时要注意数量上限,当数量超过99时,应该显示"99+"而不是具体数字,避免数字过长影响布局。

七、响应式底部导航栏

适应不同屏幕尺寸

dart 复制代码
class ResponsiveBottomNavPage extends StatefulWidget {
  const ResponsiveBottomNavPage({super.key});

  @override
  State<ResponsiveBottomNavPage> createState() => _ResponsiveBottomNavPageState();
}

class _ResponsiveBottomNavPageState extends State<ResponsiveBottomNavPage> {
  int _currentIndex = 0;

  final List<Widget> _pages = [
    _buildPage('首页', Colors.blue),
    _buildPage('发现', Colors.green),
    _buildPage('消息', Colors.orange),
    _buildPage('我的', Colors.purple),
  ];

  @override
  Widget build(BuildContext context) {
    final screenWidth = MediaQuery.of(context).size.width;
    final isWideScreen = screenWidth > 600;

    return Scaffold(
      appBar: AppBar(
        title: const Text('响应式底部导航'),
        backgroundColor: Colors.cyan,
        foregroundColor: Colors.white,
      ),
      body: isWideScreen
          ? Row(
              children: [
                NavigationRail(
                  selectedIndex: _currentIndex,
                  onDestinationSelected: (index) {
                    setState(() {
                      _currentIndex = index;
                    });
                  },
                  labelType: NavigationRailLabelType.all,
                  destinations: const [
                    NavigationRailDestination(
                      icon: Icon(Icons.home),
                      selectedIcon: Icon(Icons.home),
                      label: Text('首页'),
                    ),
                    NavigationRailDestination(
                      icon: Icon(Icons.explore),
                      selectedIcon: Icon(Icons.explore),
                      label: Text('发现'),
                    ),
                    NavigationRailDestination(
                      icon: Icon(Icons.message),
                      selectedIcon: Icon(Icons.message),
                      label: Text('消息'),
                    ),
                    NavigationRailDestination(
                      icon: Icon(Icons.person),
                      selectedIcon: Icon(Icons.person),
                      label: Text('我的'),
                    ),
                  ],
                ),
                Expanded(child: _pages[_currentIndex]),
              ],
            )
          : _pages[_currentIndex],
      bottomNavigationBar: isWideScreen
          ? null
          : BottomNavigationBar(
              currentIndex: _currentIndex,
              onTap: (index) {
                setState(() {
                  _currentIndex = index;
                });
              },
              type: BottomNavigationBarType.fixed,
              items: const [
                BottomNavigationBarItem(
                  icon: Icon(Icons.home),
                  label: '首页',
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.explore),
                  label: '发现',
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.message),
                  label: '消息',
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.person),
                  label: '我的',
                ),
              ],
            ),
    );
  }

  Widget _buildPage(String title, Color color) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.devices, size: 80, color: color),
          const SizedBox(height: 20),
          Text(
            title,
            style: TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
              color: color,
            ),
          ),
          const SizedBox(height: 10),
          Text(
            '屏幕宽度: ${MediaQuery.of(context).size.width.toStringAsFixed(0)}',
            style: TextStyle(color: Colors.grey[600]),
          ),
        ],
      ),
    );
  }
}

响应式设计原则

在不同尺寸的设备上,底部导航栏的展示方式也应该有所区别:

  1. 小屏幕(<600dp):使用BottomNavigationBar,位于底部
  2. 大屏幕(>=600dp):使用NavigationRail,位于左侧
  3. 超宽屏:可以考虑使用导航抽屉或者顶部的Tab栏

响应式设计可以提高应用在各种设备上的用户体验,让应用界面更加合理和高效。

八、BottomNavigationBar最佳实践

实践总结表

实践要点 说明 优先级
导航项数量 3-5个为最佳,避免过多 高
图标选择 使用Material Icons,清晰易识别 高
标签简洁 每个标签1-2个字,中文优先 高
视觉反馈 选中状态要有明显区别 高
徽标管理 及时清理,避免数字过大 中
类型选择 根据设计需求选择fixed或shifting 中
响应式 大屏考虑使用侧边导航 中
动画流畅 切换时使用流畅的过渡动画 低

关键实践建议

  1. 合理规划导航项:底部导航栏最适合展示应用的主要功能入口,通常建议3-5个。如果需要更多导航项,应该考虑使用Drawer或者其他导航方式。

  2. 图标和文字搭配:图标应该简洁明了,易于识别。标签文字应该简短,最好不超过2个汉字或4个字母。图标和文字应该相互补充,用户即使不看文字也能理解图标含义。

  3. 状态管理优化:对于每个页面的内容,应该使用独立的状态管理方式,避免不必要的重建。可以考虑使用IndexedStack来保持页面状态。

  4. 性能考虑:底部导航栏的页面切换应该快速流畅。如果页面内容复杂,可以考虑预加载或者使用懒加载策略。

  5. 测试不同场景:在实际设备上测试底部导航栏的表现,包括不同屏幕尺寸、不同方向(横竖屏)、不同主题(深色/浅色模式)等场景。

通过遵循这些最佳实践,可以创建出既美观又实用的底部导航栏,为用户提供优秀的导航体验。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
李游Leo5 小时前
HarmonyOS 7 DualCart 平行视界适配实录 04:EasyGo × 虚拟容器:商品比价双详情与分栏比例策略【鸿蒙心迹】
华为·harmonyos
李游Leo6 小时前
HarmonyOS 7 PixelBridge 原生库适配实录 06:Release 性能基线、资源释放、包体积与工程化验收【鸿蒙心迹】
华为·harmonyos
传奇开心果编程6 小时前
【ArkUI进阶练中学】第11课:安全与合规进阶
学习·ui·华为·harmonyos
熊猫钓鱼>_>8 小时前
Kotlin Multiplatform for OpenHarmony 实战:为 Reaktive 实现响应式原语适配(完整版 · 含摘要目录与技术图表)
华为·kotlin·大模型·ai编程·harmonyos·适配·reaktive
李游Leo11 小时前
HarmonyOS 7 + Spatial Recon Kit-C++:3DGS 高斯参数的非有限值隔离与可渲染性门禁【鸿蒙心迹】
开发语言·c++·3d·harmonyos
熊猫钓鱼>_>11 小时前
Kotlin Multiplatform for OpenHarmony 实战:为 MVIKotlin 实现单向数据流适配
华为·ai编程·鸿蒙·openharmony·适配·数据流·mvkotlin
传奇开心果编程11 小时前
【ArkUI进阶练中学】第12课:应用架构演进与遗留系统迁移
学习·ui·华为·harmonyos
传奇开心果编程13 小时前
【ArkUI进阶练中学】第13课:元服务与卡片开发
学习·ui·华为·harmonyos
李游Leo14 小时前
HarmonyOS 7 InsightBoard 多形态适配实录 06:ArkUI × 多设备回归:布局抖动、监听释放与全形态验收【鸿蒙心迹】
回归·kotlin·harmonyos