事件分发机制
- 命中测试
- 事件分发
- 事件清理
命中测试
/// A RenderProxyBox subclass that allows you to customize the
/// hit-testing behavior.
abstract class RenderProxyBoxWithHitTestBehavior extends RenderProxyBox{
@override
bool hitTest(BoxHitTestResult result, { required Offset position }) {
bool hitTarget = false;
if (size.contains(position)) {
hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
if (hitTarget || behavior == HitTestBehavior.translucent) {
result.add(BoxHitTestEntry(this, position));
}
}
return hitTarget;
}
@override
bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque;
...
}
- 手指按下触发
PointerDownEvent事件,按照深度优先开始进行命中测试 - 如果子组件命中返回为ture,则先被加入命中结果数组,其父组件同样也会返回true,并加入命中结果数组。这一点在接口
RenderProxyBoxWithHitTestBehavior的hitTest方法中体现——hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position))。 - 这里要注意如果是多个子组件,是如何去判断命中的。这里我们看一下RenderBoxContainerDefaultsMixins的实现:
bool defaultHitTestChildren(BoxHitTestResult result, { required Offset position }) {
ChildType? child = lastChild;
while (child != null) {
// The x, y parameters have the top left of the node's box as the origin.
final ParentDataType childParentData = child.parentData! as ParentDataType;
final bool isHit = result.addWithPaintOffset(
offset: childParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - childParentData.offset);
return child!.hitTest(result, position: transformed);
},
);
if (isHit) {
return true;
}
child = childParentData.previousSibling;
}
return false;
}
多个子组件,遍历子组件(从后往前遍历),有命中的就终止遍历,返回结果。
为什么从后往前(previousSibling),而不是从前往后(nextSibling)?
自问自答:我个人理解是命中区域会有重叠,如Stack。当Stack有重叠的时候,最上层显示children中靠后的,当然事件也优先让他们去命中测试。
命中测试当前作用手机了命中结果数组HitTestResult。
事件分发
GestureBinding类为单例类,集成自BindingBase,并从BindingBase中获取platformDispatcher,并对platformDispatcher的触摸事件处理进行方法进行赋值。 事件分发调用是由platformDispatcher单例去管理的。
ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance
GestureBinding管理Queue,调用_handlePointerEventImmediately并在合适时机遍历分发event到目标上,包括hitTest命中测试方法,
dispatchEvent方法先判断事件类型,如果HitTestResult有结果,遍历调用HitTestTarget的handleEvent方法,组件只需要重新handleEvent方法去处理事件就好。对于我们日常用的Listener、GestureDetector(Listener的封装)均在Listener的createRenderObject方法返回的RenderPointerListener中实现:
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
assert(debugHandleEvent(event, entry));
if (event is PointerDownEvent) {
return onPointerDown?.call(event);
}
if (event is PointerMoveEvent) {
return onPointerMove?.call(event);
}
if (event is PointerUpEvent) {
return onPointerUp?.call(event);
}
if (event is PointerHoverEvent) {
return onPointerHover?.call(event);
}
if (event is PointerCancelEvent) {
return onPointerCancel?.call(event);
}
if (event is PointerPanZoomStartEvent) {
return onPointerPanZoomStart?.call(event);
}
if (event is PointerPanZoomUpdateEvent) {
return onPointerPanZoomUpdate?.call(event);
}
if (event is PointerPanZoomEndEvent) {
return onPointerPanZoomEnd?.call(event);
}
if (event is PointerSignalEvent) {
return onPointerSignal?.call(event);
}
}
handleEvent根据具体的事件类型,调用传入的回调方法。
至此事件完整的分发出去了。
事件清理
事件清理也是在_handlePointerEventImmediately方法中进行,当收到up、cancel等事件后,进行相应的分发,并从hitTestResult中移除
记录个人学习过程,不对的地方欢迎大佬指正