0%

Flutter事件机制

事件分发机制

  1. 命中测试
  2. 事件分发
  3. 事件清理

命中测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// 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,并加入命中结果数组。这一点在接口RenderProxyBoxWithHitTestBehaviorhitTest方法中体现——hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position))。
  • 这里要注意如果是多个子组件,是如何去判断命中的。这里我们看一下RenderBoxContainerDefaultsMixins的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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单例去管理的。

1
ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance

GestureBinding管理Queue,调用_handlePointerEventImmediately并在合适时机遍历分发event到目标上,包括hitTest命中测试方法,

dispatchEvent方法先判断事件类型,如果HitTestResult有结果,遍历调用HitTestTargethandleEvent方法,组件只需要重新handleEvent方法去处理事件就好。对于我们日常用的ListenerGestureDetector(Listener的封装)均在ListenercreateRenderObject方法返回的RenderPointerListener中实现:

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
@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方法中进行,当收到upcancel等事件后,进行相应的分发,并从hitTestResult中移除

记录个人学习过程,不对的地方欢迎大佬指正