前言
点击事件从原生给出的回调开始处理,从PointerDownEvent开始收集命中的节点,到PointerUpEvent选出胜利者并响应点击回调结束。
在实现自研框架的扩大热区功能时,如何保证被绝对定位节点遮盖、滚出视口时不响应扩大热区成为一个难点,借机捋了一遍flutter的点击,也顺利的从整个流程中找到思路。
HitTest收集命中的节点列表
本质上是通过hitTest从根节点开始收集渲染树上所有的命中的节点
挂载原生回调:window.onPointerDataPacket = _handlePointerDataPacket;
点击时调用hitTest,收集hitTestResult
hitTestResult = HitTestResult();
hitTest(hitTestResult, event.position);
RenderView 是 Flutter 渲染树的根节点(根渲染对象)
从根节点开始收集:renderView.hitTest(result, position: position);
调用子节点的hitTest
最终调用:RenderBox基类中的hitTest方法,判断position是否在当前的节点坐标范围内
其中RenderBox的hitTest,hitTestChildren和hitTestSelf可能会被override,比如stack重写了hitTestChildren
最终result中存储了所有坐标被position命中的节点,包括它自己,GestureBinding把自己加在了队列的末尾,用于最终的事件分发
/// Determine which [HitTestTarget] objects are located at a given position.
@override // from HitTestable
void hitTest(HitTestResult result, Offset position) {
result.add(HitTestEntry(this));
}
hitTestResult不为空则循环调用集合中每一个对象的handleEvent方法,当然不是所有的节点都会消费此方法
例如GestureDetector最终由PrimaryPointerGestureRecognizer处理handleEvent,它的作用是调用手势竞技场的add方法,把自己作为一个参与者加入
候调用sweep(打扫)
手势竞技
手势竞技决定最终由哪个节点响应点击事件
最终执行到GestureBinding的handleEvent时,会调用手势竞技场实例的相关方法决出胜利者
gestureArena是手势竞技场的实例,可以看作是本场手势竞技的法官,在down的事件时调用close,up的时候调用sweep(打扫)
调用close把竞技场的状态打开状态置为false,并尝试去把这次竞技结束,并不一定能真的决出一个胜利者
只有在up的时候才能真的去决出胜利者,调用sweep(打扫)竞技场
sweep中流程很简单,就是让竞争者中的第一位直接获取胜利,其他的拒绝响应。而竞争者中的第一个,就是就是Widget树中,最深的手势响应者。
响应点击
上述手势竞技中决出了胜利者,并调用了acceptGesture。其余命中组件调用rejectGesture
在GestureDetect组件中的acceptGesture源码如下,标记自己在本次竞技中获取胜利,然后调用checkDown和checkUp
checkDown和checkUp分别对应响应handleTapDown和handleTapUp,其中handleTapUp会check _up是否存在,不存在则不会响应handleTapUp。
_up是在上面handlePrimaryPointer中赋值,识别为move不会赋值
最后,handleTapDown中响应onTapDown
handleTapUp中响应onTapUp、onTap,完成一次点击事件的识别。
参考
大佬的文章介绍的很详细:深入进阶-从一次点击探寻Flutter事件分发原理