likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 71

哈喽,我是老刘

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

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

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,  
      ),  
    );  
  }  
}  

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

接下来实现一个自定义的导航栏传递给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,  
        ),  
      ),  
    );  
  }  
}

我们来看一下效果 Flutter如何优雅的实现闲鱼首页底部导航栏

可以看到我们虽然给中间的特殊按钮设置高度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的兴趣,欢迎联系老刘,我们互相学习。 点击免费领老刘整理的《Flutter开发手册》,覆盖90%应用开发场景。 可以作为Flutter学习的知识地图。 覆盖90%开发场景的《Flutter开发手册》

转载自:https://juejin.cn/post/7370357521897390092
评论
请登录