Flutter 双向聊天列表效果进阶优化

举报
GSYTech 发表于 2022/06/02 00:01:52 2022/06/02
【摘要】 聊天列表是一个很扣细节的场景,在之前的 《Flutter 实现完美的双向聊天列表效果,滑动列表的知识点》 里,通过 CustomScrollView 和配置它的 center 从而解决了数据更新时的列表跳...

聊天列表是一个很扣细节的场景,在之前的 《Flutter 实现完美的双向聊天列表效果,滑动列表的知识点》 里,通过 CustomScrollView 和配置它的 center 从而解决了数据更新时的列表跳动问题,但是这时候又有网友提出了新的问题:

如下动图所示,可以看到虽然列表在添加新数据后虽然没有发生跳动,但是在列表数据长度足够的情况下,顶部会有一篇空白。

如下代码所示,这个问题的起因正是在解决跳动问题而增加的 center ,因为列表是 reverse ,并且红色的 SliverList 长度只有 3 条,高度不够导致顶部留空白。

CustomScrollView(
        controller: scroller,
        reverse: true,
        center: centerKey,
        slivers: [
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                var item = newData[index];
                if (item.type == "Right")
                  return renderRightItem(item);
                else
                  return renderLeftItem(item);
              },
              childCount: newData.length,
            ),
          ),
          SliverPadding(
            padding: EdgeInsets.zero,
            key: centerKey,
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                var item = loadMoreData[index];
                if (item.type == "Right")
                  return renderRightItem(item);
                else
                  return renderLeftItem(item);
              },
              childCount: loadMoreData.length,
            ),
          ),
        ],
      )



  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

如下图结合图片理解更形象:

  • center 其实就是列表的起始锚点,我们把锚点给了 SliverPadding ,而因为列表是 reverse,所以起始位置是在屏幕下方;
  • 红色的 old 数据 SliverList ,在代码里是处于 center 的下方,而因为 reverse 所以它实际就是黄色的部分;
  • 所以虽然绿色的 SliverList 虽然新增了数据,但是从 center 往上的高度还是不够,所以就出现了黄色 SliverList 顶部空白的问题;

结合这个问题,这里可以发现关键的点就在于 reverse ,而对比微信和QQ的聊天列表需求,在没有数据时,消息数据应该是从顶部开始,所以这时候就需要我们调整列表实现,参考微信/QQ 的实现模式。

如下代码所以,这里针对新交互场景做了优化调整:

  • 去除 CustomScrollViewreverse
  • 对调两个 SliverList 的位置,把加载 old 数据的 SliverList 放到 center 的前面;
CustomScrollView(
  controller: scroller,
  center: centerKey,
  slivers: [
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) {
          var item = loadMoreData[index];
          if (item.type == "Right")
            return renderRightItem(item);
          else
            return renderLeftItem(item);
        },
        childCount: loadMoreData.length,
      ),
    ),
    SliverPadding(
      padding: EdgeInsets.zero,
      key: centerKey,
    ),
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) {
          var item = newData[index];
          if (item.type == "Right")
            return renderRightItem(item);
          else
            return renderLeftItem(item);
        },
        childCount: newData.length,
      ),
    ),
  ],
)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

是不是很简单,就这?运行后也如下图所示,可以看到运行后的代码不会再有空白的情况,也没有新增数据跳动的情况,双向滑动也正常,那你知道为什么吗?

如下图所示,调整后从结构上变成了右边的逻辑:

  • 数据起始锚点在页面顶部,所以不会存在顶部留空问题;
  • center 下面的 SliverList 按照正向排序正常显示,用于显示新数据;
  • center 上面的 SliverList 列表会被变成以 center 为起点反向顺序显示,用于加载旧数据;

当然,这里有一点需要注意的局就是:起始进来时加载的第一页数据应该是用绿色的正向 SliverList ,因为起始点在顶部,如果不用下面绿色的正向 SliverList ,就会导致第一次数据看不到的情况

这时候就有人可能会说,如果是下图所示场景,只加载旧数据,不加载新数据,那不就出现底部留空了吗?

是的,我们其实是把顶部留空的问题转移到了底部,但是这个问题在实际业务场景是不成立,进入聊天列表首先就需要先加载满一页的数据,所以:

  • 如果 old 数据本来就不够,例如例子里只有3条,那也就不会有加载更多 old 数据的场景,所以不会产生滑动;
  • 如果 old 数据足够,那默认就足以撑满列表;

而随着 new 数据的增加,页面也会被填满从而可以正常滑动并且充满,所以从这个实现上看会更加合理。

那有人可能会说,就这?还有什么可以优化的小技巧? 比如增加判断列表是否处于底部,决定在接受到新数据时是否滑动到最新消息。

实现这个优化也很简单,首先我们可以嵌套一个 NotificationListener , 在这里我们主要是获取 notification.metrics.extentAfter 这个参数。

NotificationListener(
  onNotification: (notification) {
    if (notification is ScrollNotification) {
      if (notification.metrics is PageMetrics) {
        return false;
      }
      if (notification.metrics is FixedScrollMetrics) {
        if (notification.metrics.axisDirection == AxisDirection.left ||
            notification.metrics.axisDirection == AxisDirection.right) {
          return false;
        }
      }
      
      ///取到这个值
      extentAfter = notification.metrics.extentAfter;
    }
    return false;
  },
)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这里的 if 判断,只是为了规避其他控件的影响,比如列表里的 PageView 或者 TextFiled 的影响。

extentAfter 参数的作用是什么? 事实上在 FixedScrollMetrics 里有 extentBeforeextentInsideextentAfter 三个参数,它们的关系类似下图所示:

一般情况下:

  • extentInside 就是视图窗口大小;
  • extentBefore 就是前面还可以滑动距离;
  • extentAfter 就是后面还可以滑动距离;

所以我们只需要判断 extentAfter 是否为 0 ,就可以判断列表是不是处于底部 ,从而针对场景首先不同的业务逻辑,例如下图所示,针对列表是否处于底部,在接收到新数据时是直接跳到最新数据,还是弹出提示用让用户点击跳转。

if (extentAfter == 0) {
  ScaffoldMessenger.of(context).showSnackBar(SnackBar(
    content: Text("你目前位于最底部,自动跳转新消息item"),
    duration: Duration(milliseconds: 1000),
  ));
  Future.delayed(Duration(milliseconds: 200), () {
    scroller.jumpTo(scroller.position.maxScrollExtent);
  });
} else {
  ScaffoldMessenger.of(context).showSnackBar(SnackBar(
    content: InkWell(
      onTap: () {
        scroller.jumpTo(scroller.position.maxScrollExtent);
      },
      child:Text("点击我自动跳转新消息item")
    ),
    duration: Duration(milliseconds: 1000),
  ));
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

所以从聊天列表的场景上看,实现一个聊天列表并不难,但是需要优化的细节可能会很多,如果你在这方面还有什么问题,欢迎评论交流。

实例代码可见:https://github.com/CarGuo/gsy_flutter_demo/blob/master/lib/widget/chat_list_scroll_demo_page_2.dart

文章来源: carguo.blog.csdn.net,作者:恋猫de小郭,版权归原作者所有,如需转载,请联系作者。

原文链接:carguo.blog.csdn.net/article/details/123477085

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。