哈喽,我是老刘

前一篇文章我们讲了如何实现闲鱼首页的底部导航栏
Flutter如何优雅的实现闲鱼首页底部导航栏 - 知乎 (zhihu.com)

今天我们来讲解如何实现闲鱼首页的页面内容
其实主要就是各种不同滑动效果的嵌套

我们先来看一下整个页面的结构

页面结构分析

image.png
其实不考虑底下的导航栏的话,页面总体可以分为两部分:
上面第一部分是固定的头部
这部分是不随着滑动变化的,搜索栏点击后会跳转新页面
所以整个第一部分很简单,就不赘述了

第二部分整体是一个可以上下滑动的ListView
这个ListView内又可以分为4部分
image.png

第一行:可以横向滑动的按钮栏

第二行:可以横向滑动的卡片栏
整体来说这个和第一行是一样的
但是有一点略微复杂
如果大家仔细观察就会发现,卡片在滑动时,其中的图片会有一个动画效果
动画的原理我们讲过
每一帧都会调用一个你写的回调,传入滑动偏移量等信息
所以每一帧都在回调中根据滑动偏移量计算图片的大小及位置即可
这个也不复杂,就不展开讲了
如果有同学需要讲解这部分,可以评论区留言,我到时候再单独写篇文章

第三行:分类的tab栏
这个也是可以横向滑动的
但是这里有一个特殊效果要注意,
就是随着向上滑动,最终这个tab栏会吸顶

第四行:纵向滑动的内容瀑布流

所以前三行其实都是横向滑动的效果,嵌入到一个纵向滑动的ListView中
接下来我们来一步步实现这个页面效果

实现页面框架

首先来实现一下顶部三行?瀑布流的页面框架
今天这个方案可能稍微有一点不正规
但是它非常符合我们正常的思维逻辑

我们知道在Flutter中,如果想实现多个ListView组合嵌套的效果,通常可以用Sliver系列组件实现
而想要实现吸顶效果,可以使用SliverAppBar组件
那么瀑布流上面的三行内容是不是可以通过放置三个SliverAppBar来实现呢?
我们来试一下

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('闲鱼首页示例'),
      ),
      body: CustomScrollView(
        slivers: <Widget>[
                    const SliverAppBar(
            flexibleSpace: FlexibleSpaceBar(
              title: Text('AppBar 1'),
              background: ColoredBox(color: Colors.orange),
            ),
          ),
          const SliverAppBar(
            flexibleSpace: FlexibleSpaceBar(
              title: Text('AppBar 2'),
              background: ColoredBox(color: Colors.yellow),
            ),
          ),
          const SliverAppBar(
            pinned: true,
            flexibleSpace: FlexibleSpaceBar(
              title: Text('AppBar 3 吸顶效果'),
              background: ColoredBox(color: Colors.green),
            ),
          ),
          // 瀑布流布局
          SliverGrid(
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              crossAxisSpacing: 10,
              mainAxisSpacing: 10,
              childAspectRatio: 0.7,
            ),
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                return Container(
                  color: Colors.primaries[index % Colors.primaries.length],
                  child: Center(
                    child: Text('Item $index'),
                  ),
                );
              },
              childCount: 30, // 假设有30个item
            ),
          ),
        ],
      ),
    );
  }
}

我们通过CustomScrollView放置三个SliverAppBar和一个基于SliverGrid实现的瀑布流
其中第三个SliverAppBar设置了吸顶
来看一下效果
Screen_recording_20240523_095934 00_00_00-00_00_30.gif

可以看到,同时放置三个SliverAppBar是没有问题的
但是要注意,SliverAppBar 本身支持多种效果比如悬浮、缩放等等
这些效果都是针对单一AppBar的场景设计的
当我们同时放置多个SliverAppBar时一定要注意各种特效的冲突
这也是我前面说这种用法稍微有点不正规的原因

接下来就要把AppBar中的内容改成横向滑动的列表

SliverAppBar 中的横向滑动

image.png

我们仔细观察这三行横向滑动部分会发现
其实前两行只是按钮,点击后会跳转到新页面
而第三行实际上是一个TabBar,点击后会改变瀑布流的内容
所以这里我们可以用两种方案来实现横向滑动效果

前两行的横向滑动按钮栏,可以通过ListView来实现

SliverAppBar(
    flexibleSpace: FlexibleSpaceBar(
      title: ListView.builder(
        scrollDirection: Axis.horizontal, // 设置ListView为横向滑动
        itemCount: 10,
        itemBuilder: (context, index) {
          return Container(
            alignment: Alignment.center,
            padding: EdgeInsets.all(10),
            child: Text('Button $index'),
          );
        },
      ),
      titlePadding: EdgeInsets.zero,
      background: const ColoredBox(color: Colors.orange),
    ),
  )

把第一个 SliverAppBar 的 title 部分替换成ListView
要注意 title 部分默认带有Padding,所以需要通过 titlePadding 属性去掉缩进
来看一下效果
Screen_recording_20240523_105401 00_00_00-00_00_30.gif

好的,这样前两行的横向滑动效果就有了
只需要把 Container 中的内容替换成按钮并增加点击事件即可

下面我们来看一下第三行的TabBar
TabBar 需要配合 TabController 使用
所以需要先把 HomePage 的类型从 StatelessWidget 替换为 StatefulWidget
这个操作IDE有自动化方式,不需要手动修改
image.png

代码如下

    class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State createState() => _HomePageState();
}

class _HomePageState extends State with TickerProviderStateMixin {
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 10, vsync: this);
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('闲鱼首页示例'),
      ),
      body: CustomScrollView(
        slivers: [
          SliverAppBar(
            // AppBar 1
          ),
          const SliverAppBar(
            // AppBar 2
          ),
          SliverAppBar(
            pinned: true,
            flexibleSpace: FlexibleSpaceBar(
              title: TabBar(
                controller: _tabController,
                isScrollable: true,
                tabs: List.generate(
                  10,
                  (index) => Tab(text: 'Tab $index'),
                ),
              ),
              titlePadding: EdgeInsets.zero,
              background: const ColoredBox(color: Colors.green),
            ),
          ),
          // 瀑布流布局
          SliverGrid(
            // ...
          ),
        ],
      ),
    );
  }
}

这里TabBar的使用就不详细赘述了
来看下效果
Screen_recording_20240523_110951 00_00_00-00_00_30.gif

那么到这里整个页面瀑布流上方的三个横向滑动栏就完成了

接下来我们看看瀑布流部分如何实现

瀑布流

好吧,实现瀑布流比较麻烦
这里偷个懒吧,直接找三方库
现在 Flutter 生态很不错,不需要重复发明轮子
不过这里要注意,需要找支持 Sliver 的三方库
我们这里以 waterfall_flow 库为例
waterfall_flow | Flutter package (pub.dev)

把瀑布流部分替换成如下代码:

SliverWaterfallFlow(
    gridDelegate: const SliverWaterfallFlowDelegateWithFixedCrossAxisCount(
      crossAxisCount: 2, // 设置列数为2
      crossAxisSpacing: 10.0, // 水平间距
      mainAxisSpacing: 10.0, // 垂直间距
    ),
    delegate: SliverChildBuilderDelegate(
      (BuildContext context, int index) {
        return Container(
            alignment: Alignment.center,
            color: Colors.teal[100 * (index % 9)],
            height: 50.0 + 40.0 * (index % 9),     // 每个元素高度不固定
            child: Text('grid item $index'));
      },
      childCount: 20,
    ),
  )

这里使用了库中的 SliverWaterfallFlow 组件
看一下效果
Screen_recording_20240523_114243 00_00_00-00_00_30.gif

好了,到这里整个页面的内容部分就全部完成了
如果结合前面一篇文章讲到的底部导航栏
我们就实现了闲鱼首页的效果

如果看到这里的同学有学习Flutter的兴趣,欢迎联系老刘,我们互相学习。
点击免费领老刘整理的《Flutter开发手册》,覆盖90%应用开发场景。
可以作为Flutter学习的知识地图。
覆盖90%开发场景的《Flutter开发手册》


程序员老刘
1 声望2 粉丝

客户端架构师,客户端团队负责人。一个月带领客户端团队从0基础迁移到Flutter 。目前团队已使用Flutter五年。