Flutter如何实现图示底部导航栏 解决Book Now按钮布局错位问题
问题描述
我尝试在Flutter中实现该样式的底部导航栏,但book now按钮始终无法正确摆放定位,请问要如何实现这种类型的底部导航栏?

实现思路
这种中间带凸起悬浮按钮的底部导航,不要直接依赖原生BottomNavigationBar组件做改造,直接自定义bottomNavigationBar的布局即可,核心用Stack层叠布局配合Positioned定位,就能稳定控制book now按钮的位置,不会出现偏移。
具体实现步骤
- 外层用
SizedBox固定底部导航总高度,常规取80-90即可,内部嵌套Stack,必须设置clipBehavior: Clip.none,否则凸起在导航栏外部的按钮部分会被父容器裁切隐藏。 Stack最底层放普通导航栏内容:带圆角、阴影的背景容器,内部用Row排列四个普通导航tab,每个tab用Expanded包裹实现均分,中间预留和凸起按钮等宽的空白位,避免tab被按钮遮挡。Stack上层放凸起的book now按钮,用Positioned定位,top属性设为负数值(一般-20到-30,根据按钮尺寸调整,让按钮一半高度露出导航栏上方),配合left:0、right:0加Center实现水平居中,按钮本身按需加装饰、点击事件即可。
核心代码示例
Scaffold( // 页面主体内容 body: const PageContent(), bottomNavigationBar: SizedBox( // 基础高度85,适配全面屏时可加上MediaQuery获取的底部安全区高度 height: 85, child: Stack( clipBehavior: Clip.none, children: [ // 底层:导航栏背景+普通tab Container( decoration: BoxDecoration( color: Colors.white, borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 10, offset: Offset(0, -2), ) ], ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildNavItem(Icons.home, "Home", isSelected: true), _buildNavItem(Icons.favorite_border, "Favorites"), // 中间预留和按钮等宽的空白位 const SizedBox(width: 60), _buildNavItem(Icons.notifications_none, "Alerts"), _buildNavItem(Icons.person_outline, "Profile"), ], ), ), // 上层:凸起的Book Now按钮 Positioned( top: -25, left: 0, right: 0, child: Center( child: GestureDetector( onTap: () { // 编写预订按钮点击逻辑 }, child: Container( width: 60, height: 60, decoration: BoxDecoration( color: Colors.blueAccent, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.blueAccent.withOpacity(0.4), blurRadius: 8, offset: const Offset(0, 4), ) ], ), child: const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.calendar_today, color: Colors.white, size: 20), SizedBox(height: 4), Text( "Book Now", style: TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold ), ) ], ), ), ), ), ) ], ), ), ) // 普通导航项封装 Widget _buildNavItem(IconData icon, String label, {bool isSelected = false}) { return Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, color: isSelected ? Colors.blueAccent : Colors.grey, size: 24), const SizedBox(height: 4), Text( label, style: TextStyle(color: isSelected ? Colors.blueAccent : Colors.grey, fontSize: 12), ) ], ), ); }
避坑说明
- 不要给
Stack额外设置对齐属性,直接通过Positioned的top负值控制凸起高度,是最稳定的定位方式,不会因为页面重绘出现位置偏移。 - 全面屏机型适配时,记得通过
MediaQuery.of(context).padding.bottom获取底部安全区高度,叠加到导航栏总高度上,避免导航栏内容被系统手势条遮挡。 - 中间预留的空白位宽度要和凸起按钮宽度保持一致,否则左右两侧tab的间距会不均匀。
内容的提问来源于stack exchange,提问作者pluto
相关产品推荐
相关产品推荐

