哈喽,我是老刘
前段时间看到有人问Flutter如何实现闲鱼首页的效果
本来觉得很简单,没啥可说的
后来仔细想想还是有两个难点的
所以打算写篇文章说明一下
结果又发现一篇文章里贴太多代码,太冗长了,而且主题也不统一
所以拆成两篇
本文主要讲如何实现底部导航栏效果
后续另一篇文章会讲页面内容,主要是滑动嵌套的部分
本文主要针对初学者,所以会有很多细节部分是如何一步步完善的
有经验的朋友可以直接看最后的代码部分
闲鱼这里是一种非常标准的首页布局
一般来说,这个页面框架直接选择Scaffold就可以,大多数页面需要的效果都能实现
这里面有一个小小的难点,就是第三个图标比整个导航栏高
注意这里第三个图标高出来的部分要覆盖在内容上面,而不能把内容顶上去
比如其它按钮高度为50,那么底部导航栏的高度就是50,内容部分和底部边缘的距离只有50
而第三个按钮高度可能是60
高出来的10的高度,需要覆盖在内容上面
有些人可能会把中间那个凸出来的按钮通过Stack单独放置在上层
这样功能上是可以实现的,但是会把底部导航栏的多个按钮割裂
从代码结构上来说不是太好
我们还是希望底部导航栏是一个独立的组件,能传递给Scaffold的bottomNavigationBar参数
我们来看看如何一步一步实现这个效果
首先来实现一个简单的页面结构
内容使用一个ListView代替,底部导航栏使用一个高50的色块代替
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
先实现一个没有做任何特殊处理的导航栏
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 大小,进而来控制底部导航栏的高度
然后利用定位子组件来绘制底部导航栏的按钮内容
实现的代码如下:
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开发手册》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。