【Flutter】自定义ListView开发记录(三)—— 处理HitTest手势事件

「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战」。

前言

在前面一篇中,已经实现了LayoutManager的基础思路结构,以及覆盖翻页的效果,但是还存在这么一个小小的问题,如下图gif所示:

有问题的.gif

当Item本身比ListView小,给Item加个点击事件;

这时候即使点击的区域不是Item所在区域,点击事件还是被响应了;

那么这篇中就来处理一下手势事件的响应问题;

分析:

回想一下ListView的结构,如果只看重点部分,按前面文章所述,差不多是这样的结构:

erDiagram
PrimaryScrollController ||--|{ Viewport : contains
Viewport ||--|{ SliverList : contains
PrimaryScrollController }

在这个体系中,下层仅仅根据上层提供的数据做出响应,上层并不知道也不关心下层具体内容,所做了什么;

所以要再这么多HitTest事件中定位要修改的地方,只能是知道具体内容、绘制位置的SliverList层;

而SliverList对HitTest所做的处理也不复杂:

image.png

去掉边界条件后,其实所做的事就是通过hitTestChildren和hitTestSelf两个方法确认是否点击自己所管理的区域;

在sliver中,hitTestSelf不重写的话,永远都是false,所以可以无视掉:

image.png

在SliverLit中,hitTestChidren方法是这么规定的:

image.png

所做的事也很简单,遍历一遍当前持有的child们,如果有人响应了hitTestBoxChilld方法,那么直接返回true;

而这个hitTestChildren方法就是真正来判断child是否被点击了的地方:

image.png

看上去是有点长,往外调用工具方法的地方也稍微有点多,细看一下也不复杂,大体流程这样的:

  • 首先判断一下方向

  • 计算出当前 item 在listView中主轴、交叉轴的位置;

  • 计算当前点击点相对位置:

    • 这步可能不太好理解,这块要结合方法数据来源,也就是ViewPort的hitTestChildren方法一起看

    image.png 最后会将具体位置,通过computeChildMainAxisPosition方法转换一下。而这个方法所做的事是这样的: image.png 说白了,就是点击位置 - child的在ListView中的绘制偏移量,但是对于ViewPort而言,child是SliverList本体,所以在上面的例子中,偏移量为0,可以无视掉;结果就是点击位置;

    而上面的 item 在 listView 中的childMainAxisPosition方法,获得的是相对当前ListView位置;

    这俩一减的结果就是点击位置 + 当前ListView的滑动偏移量,也就是点击相对位置;

  • 剩下的事就是将各种数据组装,传入 addWithOutOfBandPosition 方法;而这个addWithOutOfBandPosition方法,则会根据传入的paintOffset,更新自己的size,进而能计算是否被点击到

所以问题就在这:

按照覆盖翻页效果的规定,只有第一页正式使用了ListView的规则,其他页面则一直保持在起始位置;但这个效果仅仅是在paint方法中这么规定的,在其他地方还在使用paintOffset、layoutOffset这种,对于他们来说,还是按的是未经修改的ListView的效果来计算;

所以在这里,也要将点击位置效果处理下;

方案

要想自定义hitTest,主要是对这么四个方法进行修改,所以将其抽离到LayoutManager中:

image.png

将其默认实现改为ListView的普通实现方式;而自定义的部分,通过重写的方式进行修改,比如说在这里,覆盖翻页的LayoutManager 主要修改了这么两个地方:

hitTestChilderen 、hitTestBoxChild

首先,因为只需要响应最顶部点击到的地方,所以hitTestChildren方法,这回遍历方式先从firstChild开始;

在hitTestBoxChild方法中,所做的事也很简单:

  • 如果 childMainAxisPosition 方法拿到的相对主轴方向为负数,那么直接返回super.hitTestBoxChild,按原先逻辑处理;
  • 如果不是,那么将上面提到的计算方式逆转计算,获取点击的全局位置,直接返回child.hitTest;

结语

实现方法虽然简单,但是可以通过这个了解到在ListView中,点击事件是如何经过层层处理转换、各个parentData中的变量的含义;

最后看下修改后的效果:

没问题了.gif

可以看到,点击白色区域是彻底没有反应,点击Item区域也是正常响应,即使是重叠的部分;

猜你喜欢

转载自juejin.im/post/7030786564321968159