Flutter如何优雅的实现闲鱼首页底部导航栏

Flutter如何优雅的实现闲鱼首页底部导航栏

技术博客 admin 526 浏览

哈喽,我是老刘

前段时间看到有人问Flutter如何实现闲鱼首页的效果
本来觉得很简单,没啥可说的
后来仔细想想还是有两个难点的
所以打算写篇文章说明一下
结果又发现一篇文章里贴太多代码,太冗长了,而且主题也不统一
所以拆成两篇
本文主要讲如何实现底部导航栏效果
后续另一篇文章会讲页面内容,主要是滑动嵌套的部分

本文主要针对初学者,所以会有很多细节部分是如何一步步完善的
有经验的朋友可以直接看最后的代码部分

闲鱼这里是一种非常标准的首页布局
一般来说,这个页面框架直接选择Scaffold就可以,大多数页面需要的效果都能实现
这里面有一个小小的难点,就是第三个图标比整个导航栏高
注意这里第三个图标高出来的部分要覆盖在内容上面,而不能把内容顶上去
比如其它按钮高度为50,那么底部导航栏的高度就是50,内容部分和底部边缘的距离只有50
而第三个按钮高度可能是60
高出来的10的高度,需要覆盖在内容上面

有些人可能会把中间那个凸出来的按钮通过Stack单独放置在上层
这样功能上是可以实现的,但是会把底部导航栏的多个按钮割裂
从代码结构上来说不是太好
我们还是希望底部导航栏是一个独立的组件,能传递给Scaffold的bottomNavigationBar参数
我们来看看如何一步一步实现这个效果

首先来实现一个简单的页面结构

内容使用一个ListView代替,底部导航栏使用一个高50的色块代替

dart
复制代码
class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: ListView.builder( itemCount: 100, // 设置列表项的数量为100 itemBuilder: (context, index) { return Container( height: 40, color: Color(0xFF888000 + index * 20), // 使用当前索引对应的颜色 alignment: Alignment.center, // 将内容居中 child: Text('Row $index', style: const TextStyle(color: Colors.white)), ); }, ), bottomNavigationBar: Container( height: 50, color: Colors.blue, ), ); } }

效果如下:

接下来实现一个自定义的导航栏传递给bottomNavigationBar

先实现一个没有做任何特殊处理的导航栏

dart
复制代码
class CustomBottomNavigationBar extends StatefulWidget { const CustomBottomNavigationBar({super.key}); @override CustomBottomNavigationBarState createState() => CustomBottomNavigationBarState(); } class CustomBottomNavigationBarState extends State { int _selectedIndex = 0; void _onItemTapped(int index) { setState(() { _selectedIndex = index; }); } @override Widget build(BuildContext context) { return Container( height: 50, // 导航栏高50` ` clipBehavior: Clip.none, color: Colors.blue, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildNavItem(Icons.home, 0), _buildNavItem(Icons.business, 1), _buildNavItem(Icons.add, 2, isSpecial: true), _buildNavItem(Icons.settings, 3), _buildNavItem(Icons.person, 4), ], ), ); } Widget _buildNavItem(IconData icon, int index, {bool isSpecial = false}) { return GestureDetector( onTap: () => _onItemTapped(index), child: Container( height: isSpecial ? 60 : 40, // 特殊按钮高度60,其余40 width: isSpecial ? 60 : 40, decoration: const BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), child: Icon( icon, color: _selectedIndex == index ? Colors.amber[800] : Colors.grey, ), ), ); } }

我们来看一下效果

可以看到我们虽然给中间的特殊按钮设置高度60,但是由于整个容器的高度是50,所以中间的按钮被缩小到50了
那么怎么让中间这个按钮突破50的限制呢?
很多人可能想到了使用 OverflowBox 甚至 UnconstrainedBox
这两种组件在这个场景下都会有自己的问题或者不方便的地方,感兴趣的同学可以试一下

其实这里可以巧妙的利用 Stack 组件大小计算的原理来解决这个问题
原理:如果Stack中的所有子组件都没有定位(即没有使用Positioned包裹),那么Stack的大小将会适应其最大的子组件的大小。如果Stack中有定位的子组件,那么它们不会影响Stack的大小,Stack的大小将由未定位的子组件决定。

基于这个原理,我们可以利用未定位的子组件控制 Stack 大小,进而来控制底部导航栏的高度
然后利用定位子组件来绘制底部导航栏的按钮内容
实现的代码如下:

dart
复制代码
class CustomBottomNavigationBar extends StatefulWidget { const CustomBottomNavigationBar({super.key}); @override CustomBottomNavigationBarState createState() => CustomBottomNavigationBarState(); } class CustomBottomNavigationBarState extends State { int _selectedIndex = 0; void _onItemTapped(int index) { setState(() { _selectedIndex = index; }); } @override Widget build(BuildContext context) { return Stack( clipBehavior: Clip.none, children: [ Container( height: 50, // 导航栏高度 color: Colors.white, // 导航栏背景 ), Positioned( bottom: 0, left: 0, right: 0, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildNavItem(Icons.home, 0), _buildNavItem(Icons.business, 1), _buildNavItem(Icons.add, 2, isSpecial: true), _buildNavItem(Icons.settings, 3), _buildNavItem(Icons.person, 4), ], ), ), ], ); } Widget _buildNavItem(IconData icon, int index, {bool isSpecial = false}) { return GestureDetector( onTap: () => _onItemTapped(index), child: Container( height: isSpecial ? 60 : 40, // 特殊按钮高度60,其余40 width: isSpecial ? 60 : 40, decoration: const BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), child: Icon( icon, color: _selectedIndex == index ? Colors.amber[800] : Colors.grey, ), ), ); } }

这里面主要就是把导航栏的主体改成了 Stack
利用其中未定位的子组件 Conatiner 设置导航栏的高度及背景
利用定位的 Row 绘制导航栏的按钮部分
这样不管按钮部分绘制多大,都不会影响导航栏的大小
效果如下:

总结

好了,模仿闲鱼首页的底部导航栏部分的实现原理就先介绍到这里了
下一篇文章我们介绍内容部分如何实现嵌套滑动
如果看到这里的同学有学习Flutter的兴趣,欢迎联系老刘,我们互相学习。
点击免费领老刘整理的《Flutter开发手册》,覆盖90%应用开发场景。
可以作为Flutter学习的知识地图。
覆盖90%开发场景的《Flutter开发手册》

源文:Flutter如何优雅的实现闲鱼首页底部导航栏

如有侵权请联系站点删除!

技术合作服务热线,欢迎来电咨询!