Flutter 底部导航栏组件实现详解
欢迎加入开源鸿蒙跨平台社区→ https://openharmonycrosplatform.youkuaiyun.com
一、组件概述
底部导航栏(Bottom Navigation Bar)是移动应用中最常用的导航模式之一,通常位于屏幕底部,用于在应用的主要功能模块之间进行切换。本文将详细介绍如何在 Flutter 中实现一个功能丰富、样式多样的底部导航栏组件,支持四种不同的视觉风格。
二、核心功能特性
本组件具备以下核心特性:
四种导航样式 :提供标准、浮动、胶囊、霓虹四种风格,满足不同设计需求。
未读消息角标 :支持显示未读消息数量,超过99显示"99+"。
选中状态动画 :图标切换和文字变化都有平滑的动画过渡效果。
自定义图标颜色 :每个导航项可以单独设置颜色,如收藏使用红色。
暗色模式适配 :组件会自动根据系统主题切换颜色方案。
三、数据模型设计
首先定义导航项的数据模型:
class BottomNavItem {
final IconData icon;
final IconData? activeIcon;
final String label;
final Color? color;
final int badgeCount;
const BottomNavItem({
required this.icon,
this.activeIcon,
required this.label,
this.color,
this.badgeCount = 0,
});
}
icon 是默认图标,activeIcon 是选中状态图标,label 是文字标签,color 可自定义颜色,badgeCount 用于显示角标数量。
四、样式枚举定义
通过枚举定义四种导航样式:
enum BottomNavStyle { standard,
floating, pill, neon }
standard 是标准样式,floating 是浮动圆角样式,pill 是胶囊样式(选中时展开显示文字),neon 是霓虹发光样式。
五、组件主体实现
组件主体继承自 StatelessWidget,通过构造函数接收配置参数:
class CustomBottomNavBar extends
StatelessWidget {
final List<BottomNavItem> items;
final int selectedIndex;
final ValueChanged<int>? onTap;
final BottomNavStyle style;
final Color? backgroundColor;
final double elevation;
final bool showLabels;
final double iconSize;
const CustomBottomNavBar({
super.key,
required this.items,
this.selectedIndex = 0,
this.onTap,
this.style = BottomNavStyle.
standard,
this.backgroundColor,
this.elevation = 8,
this.showLabels = true,
this.iconSize = 24,
});
items 是导航项列表,selectedIndex 是当前选中索引,onTap 是点击回调,style 控制样式,backgroundColor 自定义背景色,elevation 控制阴影高度,showLabels 控制是否显示文字,iconSize 控制图标大小。
六、构建方法详解
build 方法根据样式选择不同的构建方法:
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).
brightness == Brightness.dark;
final themeColor = Theme.of
(context).colorScheme.primary;
switch (style) {
case BottomNavStyle.standard:
return _buildStandard(isDark,
themeColor);
case BottomNavStyle.floating:
return _buildFloating(isDark,
themeColor);
case BottomNavStyle.pill:
return _buildPill(isDark,
themeColor);
case BottomNavStyle.neon:
return _buildNeon(isDark,
themeColor);
}
}
七、标准样式实现
标准样式是最常见的底部导航栏样式:
Widget _buildStandard(bool isDark,
Color themeColor) {
return Container(
decoration: BoxDecoration(
color: backgroundColor ??
(isDark ? const Color
(0xFF1E1E1E) : Colors.white),
boxShadow: [
BoxShadow(
color: Colors.black.
withOpacity(0.1),
blurRadius: elevation,
offset: const Offset(0,
-2),
),
],
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.
symmetric(horizontal: 8,
vertical: 8),
child: Row(
mainAxisAlignment:
MainAxisAlignment.
spaceAround,
children: List.generate
(items.length, (index) {
return _buildNavItem
(index, isDark,
themeColor);
}),
),
),
),
);
}
使用 Container 配合 BoxDecoration 实现背景色和阴影效果,SafeArea 确保内容不被系统导航栏遮挡,Row 配合 mainAxisAlignment.spaceAround 均匀分布导航项。
八、浮动样式实现
浮动样式带有圆角和更明显的阴影:
Widget _buildFloating(bool isDark,
Color themeColor) {
return Container(
margin: const EdgeInsets.
symmetric(horizontal: 16,
vertical: 8),
decoration: BoxDecoration(
color: backgroundColor ??
(isDark ? const Color
(0xFF2A2A2A) : Colors.white),
borderRadius: BorderRadius.
circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.
withOpacity(0.15),
blurRadius: 20,
offset: const Offset(0,
4),
),
],
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.
symmetric(horizontal: 8,
vertical: 12),
child: Row(
mainAxisAlignment:
MainAxisAlignment.
spaceAround,
children: List.generate
(items.length, (index) {
return _buildNavItem
(index, isDark,
themeColor);
}),
),
),
),
);
}
通过 margin 实现与屏幕边缘的间距,borderRadius 实现圆角效果,更大的阴影模糊半径增强浮动感。
九、胶囊样式实现
胶囊样式在选中时会展开显示文字标签:
Widget _buildPill(bool isDark,
Color themeColor) {
return Container(
decoration: BoxDecoration(
color: backgroundColor ??
(isDark ? const Color
(0xFF1E1E1E) : Colors.white),
boxShadow: [
BoxShadow(
color: Colors.black.
withOpacity(0.1),
blurRadius: elevation,
offset: const Offset(0,
-2),
),
],
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.
symmetric(horizontal: 16,
vertical: 12),
child: Row(
mainAxisAlignment:
MainAxisAlignment.
spaceAround,
children: List.generate
(items.length, (index) {
final isSelected =
index == selectedIndex;
final item = items
[index];
final itemColor = item.
color ?? themeColor;
return GestureDetector(
onTap: () => onTap?.
call(index),
behavior:
HitTestBehavior.
opaque,
child:
AnimatedContainer(
duration: const
Duration
(milliseconds: 200),
padding: const
EdgeInsets.symmetric
(horizontal: 20,
vertical: 10),
decoration:
BoxDecoration(
color:
isSelected ?
itemColor.
withOpacity(0.15)
: Colors.
transparent,
borderRadius:
BorderRadius.
circular(20),
),
child: Row(
mainAxisSize:
MainAxisSize.min,
children: [
Icon(
isSelected ?
(item.
activeIcon ??
item.icon) :
item.icon,
size:
iconSize,
color:
isSelected ?
itemColor :
(isDark ?
Colors.grey
[400] :
Colors.grey
[600]),
),
if
(isSelected &&
showLabels) ...[
const SizedBox
(width: 8),
Text(
item.label,
style:
TextStyle(
color:
itemColor,
fontWeight
:
FontWeight
.w600,
fontSize:
13,
),
),
],
],
),
),
);
}),
),
),
),
);
}
使用 AnimatedContainer 实现背景色和尺寸的动画过渡,选中时通过条件判断显示文字标签,形成展开效果。
十、霓虹样式实现
霓虹样式在选中图标上添加发光效果:
Widget _buildNeon(bool isDark,
Color themeColor) {
return Container(
decoration: BoxDecoration(
color: backgroundColor ??
(isDark ? const Color
(0xFF121212) : Colors.white),
boxShadow: [
BoxShadow(
color: Colors.black.
withOpacity(0.1),
blurRadius: elevation,
offset: const Offset(0,
-2),
),
],
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.
symmetric(horizontal: 8,
vertical: 12),
child: Row(
mainAxisAlignment:
MainAxisAlignment.
spaceAround,
children: List.generate
(items.length, (index) {
final isSelected =
index == selectedIndex;
final item = items
[index];
final itemColor = item.
color ?? themeColor;
return GestureDetector(
onTap: () => onTap?.
call(index),
behavior:
HitTestBehavior.
opaque,
child: Container(
padding: const
EdgeInsets.symmetric
(horizontal: 16,
vertical: 8),
child: Column(
mainAxisSize:
MainAxisSize.min,
children: [
AnimatedContaine
r(
duration:
const Duration
(milliseconds:
200),
child: Icon(
isSelected ?
(item.
activeIcon ?
? item.
icon) :
item.icon,
size:
iconSize,
color:
isSelected ?
itemColor
: (isDark ?
Colors.grey
[400] :
Colors.grey
[600]),
shadows:
isSelected
? [
Shad
ow
(col
or:
item
Colo
r.
with
Opac
ity
(0.
8),
blur
Radi
us:
12),
Shad
ow
(col
or:
item
Colo
r.
with
Opac
ity
(0.
5),
blur
Radi
us:
20),
]
: null,
),
),
if
(showLabels) ...
[
const SizedBox
(height: 4),
Text(
item.label,
style:
TextStyle(
color:
isSelected
?
itemColor
:
(isDark ?
Colors.
grey[400]
: Colors.
grey
[600]),
fontSize:
11,
fontWeight
:
isSelected
?
FontWeight
.w600 :
FontWeight
.normal,
),
),
],
],
),
),
);
}),
),
),
),
);
}
通过 Icon 的 shadows 属性添加多层 Shadow,实现霓虹发光效果。双层阴影叠加使发光更加明显。
十一、角标实现
角标用于显示未读消息数量:
if (item.badgeCount > 0)
Positioned(
right: -8,
top: -6,
child: Container(
padding: const EdgeInsets.all
(4),
decoration: const
BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
constraints: const
BoxConstraints(minWidth: 16,
minHeight: 16),
child: Text(
item.badgeCount > 99 ? '99
+' : '${item.badgeCount}',
style: const TextStyle
(color: Colors.white,
fontSize: 10, fontWeight:
FontWeight.bold),
textAlign: TextAlign.center,
),
),
),
使用 Stack 和 Positioned 将角标定位在图标右上角,红色圆形背景配合白色文字,超过99显示"99+"。
十二、使用示例
在页面中使用该组件:
Scaffold(
body: YourBodyWidget(),
bottomNavigationBar:
CustomBottomNavBar(
items: [
BottomNavItem(icon: Icons.
home_outlined, activeIcon:
Icons.home, label: '首页'),
BottomNavItem(icon: Icons.
search_outlined, activeIcon:
Icons.search, label: '搜索'),
BottomNavItem(icon: Icons.
favorite_outline, activeIcon:
Icons.favorite, label: '收藏
', color: Colors.red,
badgeCount: 3),
BottomNavItem(icon: Icons.
person_outline, activeIcon:
Icons.person, label: '我的'),
],
selectedIndex: _selectedIndex,
style: BottomNavStyle.pill,
onTap: (index) => setState(()
=> _selectedIndex = index),
),
)


十三、总结
本文实现了一个功能丰富的 Flutter 底部导航栏组件,支持四种视觉风格、未读角标、动画过渡等特性。组件设计遵循高内聚低耦合原则,通过枚举和参数配置实现灵活定制,可满足大多数应用的底部导航需求。
918



被折叠的 条评论
为什么被折叠?



