「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战」。
前言
在前面一篇中,已经实现了LayoutManager的基础思路结构,以及覆盖翻页的效果,但是还存在这么一个小小的问题,如下图gif所示:
当Item本身比ListView小,给Item加个点击事件;
这时候即使点击的区域不是Item所在区域,点击事件还是被响应了;
那么这篇中就来处理一下手势事件的响应问题;
分析:
回想一下ListView的结构,如果只看重点部分,按前面文章所述,差不多是这样的结构:
erDiagram
PrimaryScrollController ||--|{ Viewport : contains
Viewport ||--|{ SliverList : contains
PrimaryScrollController }
在这个体系中,下层仅仅根据上层提供的数据做出响应,上层并不知道也不关心下层具体内容,所做了什么;
所以要再这么多HitTest事件中定位要修改的地方,只能是知道具体内容、绘制位置的SliverList层;
而SliverList对HitTest所做的处理也不复杂:
去掉边界条件后,其实所做的事就是通过hitTestChildren和hitTestSelf两个方法确认是否点击自己所管理的区域;
在sliver中,hitTestSelf不重写的话,永远都是false,所以可以无视掉:
在SliverLit中,hitTestChidren方法是这么规定的:
所做的事也很简单,遍历一遍当前持有的child们,如果有人响应了hitTestBoxChilld方法,那么直接返回true;
而这个hitTestChildren方法就是真正来判断child是否被点击了的地方:
看上去是有点长,往外调用工具方法的地方也稍微有点多,细看一下也不复杂,大体流程这样的:
-
首先判断一下方向
-
计算出当前 item 在listView中主轴、交叉轴的位置;
-
计算当前点击点相对位置:
- 这步可能不太好理解,这块要结合方法数据来源,也就是ViewPort的hitTestChildren方法一起看
最后会将具体位置,通过computeChildMainAxisPosition方法转换一下。而这个方法所做的事是这样的:
说白了,就是点击位置 - child的在ListView中的绘制偏移量,但是对于ViewPort而言,child是SliverList本体,所以在上面的例子中,偏移量为0,可以无视掉;结果就是点击位置;
而上面的 item 在 listView 中的childMainAxisPosition方法,获得的是相对当前ListView位置;
这俩一减的结果就是点击位置 + 当前ListView的滑动偏移量,也就是点击相对位置;
-
剩下的事就是将各种数据组装,传入 addWithOutOfBandPosition 方法;而这个addWithOutOfBandPosition方法,则会根据传入的paintOffset,更新自己的size,进而能计算是否被点击到
所以问题就在这:
按照覆盖翻页效果的规定,只有第一页正式使用了ListView的规则,其他页面则一直保持在起始位置;但这个效果仅仅是在paint方法中这么规定的,在其他地方还在使用paintOffset、layoutOffset这种,对于他们来说,还是按的是未经修改的ListView的效果来计算;
所以在这里,也要将点击位置效果处理下;
方案
要想自定义hitTest,主要是对这么四个方法进行修改,所以将其抽离到LayoutManager中:
将其默认实现改为ListView的普通实现方式;而自定义的部分,通过重写的方式进行修改,比如说在这里,覆盖翻页的LayoutManager 主要修改了这么两个地方:
hitTestChilderen 、hitTestBoxChild
首先,因为只需要响应最顶部点击到的地方,所以hitTestChildren方法,这回遍历方式先从firstChild开始;
在hitTestBoxChild方法中,所做的事也很简单:
- 如果 childMainAxisPosition 方法拿到的相对主轴方向为负数,那么直接返回super.hitTestBoxChild,按原先逻辑处理;
- 如果不是,那么将上面提到的计算方式逆转计算,获取点击的全局位置,直接返回child.hitTest;
结语
实现方法虽然简单,但是可以通过这个了解到在ListView中,点击事件是如何经过层层处理转换、各个parentData中的变量的含义;
最后看下修改后的效果:
可以看到,点击白色区域是彻底没有反应,点击Item区域也是正常响应,即使是重叠的部分;