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

相关推荐
Ophelia(秃头版2 小时前
组件、页面、UIAbility、组件挂卸载的生命周期
harmonyos·arkts
一起养小猫2 小时前
Flutter实战:从零实现俄罗斯方块(一)数据结构与核心算法
数据结构·算法·flutter
ujainu2 小时前
Flutter + OpenHarmony 用户输入框:TextField 与 InputDecoration 在多端表单中的交互设计
flutter·交互·组件
●VON2 小时前
Flutter 与 OpenHarmony 应用交互优化实践:从基础列表到 HarmonyOS Design 兼容的待办事项体验
flutter·交互·harmonyos·openharmony·训练营·跨平台开发
●VON2 小时前
无状态 Widget 下的实时排序:Flutter for OpenHarmony 中 TodoList 的排序策略与数据流控制
学习·flutter·架构·交互·openharmony·von
●VON2 小时前
面向 OpenHarmony 的 Flutter 应用实战:TodoList 多条件过滤系统的状态管理与性能优化
学习·flutter·架构·跨平台·von
wqwqweee2 小时前
Flutter for OpenHarmony 看书管理记录App实战:关于我们实现
android·javascript·python·flutter·harmonyos
鸣弦artha2 小时前
Scaffold布局模式综合应用
flutter·华为·harmonyos
●VON2 小时前
Flutter for OpenHarmony:基于不可变更新与局部状态隔离的 TodoList 任务编辑子系统实现
学习·flutter·openharmony·布局·技术·von