如何实现选中的Bottom Navigation Item凸起于底部导航栏?
实现选中项凸起的底部导航栏
可以用自定义布局实现这个效果,完全不需要依赖第三方包,能避免导航栏偏移的问题,核心思路是通过Stack分层布局,让选中项单独向上凸起,同时保持底部导航栏的原有位置。
完整代码实现
自定义导航栏组件
import 'package:flutter/material.dart'; class CustomBottomNav extends StatefulWidget { final int selectedIndex; final Function(int) onTap; final List<BottomNavItemModel> items; const CustomBottomNav({ super.key, required this.selectedIndex, required this.onTap, required this.items, }); @override State<CustomBottomNav> createState() => _CustomBottomNavState(); } class _CustomBottomNavState extends State<CustomBottomNav> { @override Widget build(BuildContext context) { // 给凸起项预留额外空间,避免超出屏幕 final navHeight = kBottomNavigationBarHeight + 20; return Container( height: navHeight, color: Colors.white, child: Stack( alignment: Alignment.bottomCenter, children: [ // 底部导航栏的基础背景层(带阴影,模拟原生导航栏效果) Positioned( bottom: 0, left: 0, right: 0, height: kBottomNavigationBarHeight, child: Container( decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.2), blurRadius: 10, offset: const Offset(0, -2), ), ], ), ), ), // 导航项列表层 Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: List.generate(widget.items.length, (index) { final isSelected = index == widget.selectedIndex; return GestureDetector( onTap: () => widget.onTap(index), child: Column( mainAxisSize: MainAxisSize.min, children: [ if (isSelected) // 选中项的凸起圆形背景 + 图标 Container( width: 60, height: 60, margin: const EdgeInsets.only(bottom: 5), decoration: const BoxDecoration( color: Colors.blue, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.blue.withOpacity(0.3), blurRadius: 10, offset: Offset(0, -2), ), ], ), child: Center( child: Icon( widget.items[index].icon, color: Colors.white, size: 28, ), ), ), if (!isSelected) // 未选中项的普通图标 Icon( widget.items[index].icon, color: Colors.grey, size: 24, ), const SizedBox(height: 4), // 导航项文字 Text( widget.items[index].label, style: TextStyle( color: isSelected ? Colors.blue : Colors.grey, fontSize: 12, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, ), ), ], ), ); }), ), ], ), ); } } // 导航项数据模型,方便复用 class BottomNavItemModel { final IconData icon; final String label; BottomNavItemModel({required this.icon, required this.label}); }
使用示例
class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { // 默认选中中间项,和示例图效果匹配 int _selectedIndex = 2; final List<BottomNavItemModel> _navItems = [ BottomNavItemModel(icon: Icons.home, label: "首页"), BottomNavItemModel(icon: Icons.search, label: "搜索"), BottomNavItemModel(icon: Icons.add, label: "发布"), BottomNavItemModel(icon: Icons.message, label: "消息"), BottomNavItemModel(icon: Icons.person, label: "我的"), ]; @override Widget build(BuildContext context) { return Scaffold( body: const Center(child: Text("主内容区")), bottomNavigationBar: CustomBottomNav( selectedIndex: _selectedIndex, onTap: (index) => setState(() => _selectedIndex = index), items: _navItems, ), ); } }
关键实现细节
- 分层布局:用
Stack把导航栏背景和导航项分开,背景层保持原生底部导航栏高度,不会偏移;选中项放在上层,通过margin向上凸起。 - 预留空间:给容器设置
kBottomNavigationBarHeight + 20的高度,确保凸起的项不会超出屏幕底部,也不会挤压主内容区。 - 样式匹配:选中项添加圆形背景和阴影,和你提供的示例图效果一致,未选中项保持普通样式。
内容的提问来源于stack exchange,提问作者Arbaz Irshad
相关产品推荐
相关产品推荐

