Flutter源码探索
第一章 探索 Flutter 事件机制
文章目录
- Flutter源码探索
- 前言
- 一、事件传递
- 二、命中测试
- 三、事件分发
-
- 1. GestureBinding#dispatchEvent
- [2. HitTestEntry](#2. HitTestEntry)
- 3. RenderPointerListener#handleEvent
- 四、事件监听
-
- [1. GestureDetector](#1. GestureDetector)
- [2. RawGestureDetector](#2. RawGestureDetector)
- [3. Listener](#3. Listener)
- 4. GestureRecognizer#addPointer
- 5. OneSequenceGestureRecognizer#addAllowedPointer
- 6. GestureArenaManager#add
- 7. _GestureArena#add
- 小结
- 五、手势竞争及响应
- 总结
- 参考
前言
在 App 开发中,与用户交互的逻辑几乎是随处可见的,如:用户点击某个按钮、双击屏幕及滑动页面等。在 Flutter 中,事件机制作为其核心之一,用于响应用户输入和其他形式的交互逻辑。
Flutter 的事件机制是基于监听器模式实现的,主要通过以下几种方式来处理事件:
- 监听器 (Listener): Flutter 提供了 GestureDetector 和 RawGestureDetector 等小部件,用于监听各种手势事件,如点击、滑动、缩放等。
- 焦点节点 (FocusNode): 用于管理文本字段(如文本输入框:TextField)的焦点。通过焦点节点,你可以监听文本字段的焦点变化事件。
- 键盘事件 (Keyboard Events): 通过 RawKeyboardListener 监听键盘事件,如:按键按下和释放。
- 触摸事件 (Touch Events) 和指针事件 (Pointer Events): Flutter 提供了更底层的触摸和指针事件处理机制,例如 Listener 和 PointerListener,用于更精细的控制触摸事件。
我们知道,用户与 App 的交互行为是在原生设备上进行。以触摸事件为例,当用户在设备上触摸屏幕时,操作系统(如:Android 或 iOS)首先接收到这个触摸事件。这些事件通常以原始形式(如:触摸点的位置、压力等)被捕获。
原生层捕获到事件后,只是将所有事件打包下发,如在 Android 中,触摸手势信息被打包成 ByteBuffer ,然后从 Native 层传递到 Flutter 中,最后在 Dart 层的 _dispatchPointerDataPacket 函数中,通过 _unpackPointerDataPacket 函数解析成可用的 PointerDataPacket 对象使用。其大致流程如下图所示:

本文将跟随源码来探索 Flutter 事件传递、命中测试、事件分发、事件监听、手势竞争及响应。
一、事件传递
触摸事件在 Native 层经过转换后,通过 Tonic::DartInvoke() 拉起 Flutter 端的 _dispatchPointerDataPacket 函数,该函数就是 hooks.dart 文件的 _dispatchPointerDataPacket 函数。
Tonic::DartInvoke 是 Tonic 库中的一个函数,用于在原生层调用 Dart 代码。Tonic 库是 Flutter 框架中用于原生和Dart 代码交互的一个重要组件。它提供了丰富的 API 来支持各种原生操作,如调用 Dart 函数、处理事件等。通过 Tonic 库,开发者可以方便地在原生代码中嵌入 Dart 逻辑,实现复杂的交互和功能扩展。
1. hooks#_dispatchPointerDataPacket
dart
/// hooks.dart
@pragma('vm:entry-point')
void _dispatchPointerDataPacket(ByteData packet) {
PlatformDispatcher.instance._dispatchPointerDataPacket(packet);
}
_dispatchPointerDataPacket 函数,用于接收屏幕的点击、滑动等各种事件。类似于 Android 原生的 ViewRootImpl.java 接收 Native 层的数据。在该函数中,将接收到的事件数据传给 PlatformDispatcher 的 _dispatchPointerDataPacket 函数。
2. platform_dispatcher#_dispatchPointerDataPacket
dart
/// platform_dispatcher.dart
class PlatformDispatcher {
static PlatformDispatcher get instance => _instance;
static final PlatformDispatcher _instance = PlatformDispatcher._();
/// 当指针数据可用时调用的回调函数
/// framework 在设置回调的同一 zone 调用此回调
PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket;
PointerDataPacketCallback? _onPointerDataPacket;
Zone _onPointerDataPacketZone = Zone.root;
/// 该函数是在 GestureBinding.initInstances() 函数中调用,入参为为 GestureBinding._handlePointerDataPacket
set onPointerDataPacket(PointerDataPacketCallback? callback) {
_onPointerDataPacket = callback;
_onPointerDataPacketZone = Zone.current;
}
// Called from the engine, via hooks.dart 通过 hooks.dart 由引擎调用
void _dispatchPointerDataPacket(ByteData packet) {
if (onPointerDataPacket != null) {
_invoke1<PointerDataPacket>(
onPointerDataPacket, // 1、已经被设定为 GestureBinding._handlePointerDataPacket
_onPointerDataPacketZone, // 2、已被设定为 GestureBinding 单例执行 initInstances 函数时所在的 Zone
/// 3、_unpackPointerDataPacket 函数把 hooks.dart 中 _dispatchPointerDataPacket 函数传递来的 ByteData packet 数
/// 据整理为 PointerDataPacket 数据,即当前点击(触摸)事件的点击位置在当前屏幕空间的坐标值
_unpackPointerDataPacket(packet),
);
}
}
}
_invoke1 也是 hooks.dart 中定义的一个全局函数。在给定的 Zone zone 中调用 void Function(A a)? callback ,并传入参数 A arg ,如果传入的 Zone zone 和当前执行 _invoke1 函数不是同一个 Zone 的话,会通过调用 zone.runUnaryGuarded 函数在给定的 Zone 中调用传入的 void Function(A a)? callback。
2.1 hooks#_invoke1
dart
/// hooks.dart
void _invoke1<A>(void Function(A a)? callback, Zone zone, A arg) {
if (callback == null) { // 如果入参 callback 为 null,则直接 return
return;
}
// 如果入参 zone 和当前代码执行所在的不是一个 zone,则通过调用 Zone 的 runUnaryGuarded 函数
// 把 callback 放在入参 zone 中执行
if (identical(zone, Zone.current)) { // identical 执行严格的判等操作
callback(arg);
} else {
// runUnaryGuarded 在当前指定的 Zone 中执行给定的[action],并捕获同步错误
zone.runUnaryGuarded<A>(callback, arg);
}
}
通过上述代码分析,在 PlatformDispatcher 的 _dispatchPointerDataPacket 函数中,通过 _invoke1 函数执行 GestureBinding 的 _handlePointerDataPacket 函数,其参数为静态函数 _unpackPointerDataPacket 整理后的 PointerDataPacket 实例对象。
3. GestureBinding#_handlePointerDataPacket
dart
/// gestures/binding.dart
/// 未处理的事件队列
final Queue<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();
/// PointerDataPacket:触点对象封装了手指触摸在屏幕上的点的信息
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
/// 将 PointerData 映射为逻辑像素
try {
_pendingPointerEvents.addAll(
PointerEventConverter.expand(packet.data, _devicePixelRatioForView));
if (!locked) {
_flushPointerEventQueue();
}
} catch (error, stack) {
......// 省略部分代码
}
}
_devicePixelRatioForView 是一个辅助函数,用来获取当前设备屏幕上每个逻辑像素的设备像素数量,如:iOS 设备的 2X 和 3X。
通过 PointerEventConverter.expand(packet.data, _devicePixelRatioForView) 函数,把 PointerData 数据转化为 PointerEvent 数据。然后把转化后的 PointerEvent 添加到队列 _pendingPointerEvents (待处理的事件集合)中,待下一步调用 _flushPointerEventQueue 函数来处理。
4. GestureBinding#_flushPointerEventQueue
dart
/// gestures/binding.dart
void _flushPointerEventQueue() {
assert(!locked);
while (_pendingPointerEvents.isNotEmpty) {
handlePointerEvent(_pendingPointerEvents.removeFirst());
}
}
如果 _pendingPointerEvents 队列有数据,则不断的取出队头数据并交给 handlePointerEvent 函数来处理。
5. GestureBinding#handlePointerEvent
dart
/// gestures/binding.dart
void handlePointerEvent(PointerEvent event) {
assert(!locked);
if (resamplingEnabled) { // 如需重新采样,则重采样并返回
_resampler.addOrDispatch(event);
_resampler.sample(samplingOffset, samplingClock);
return;
}
_resampler.stop(); // 不需重新采样,则停止重采样器
_handlePointerEventImmediately(event);
}
函数内部根据布尔值 resamplingEnabled 来判断是否进行事件的重新采样,这里先不深入探讨,接下来将 PointerEvent 交给函数 _handlePointerEventImmediately 处理。
6. GestureBinding#_handlePointerEventImmediately
dart
/// gestures/binding.dart
/// 此处的 key 是 event.pointer,pointer 是不会重复的,每个 down 事件的时候会去+1
final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
void _handlePointerEventImmediately(PointerEvent event) {
HitTestResult? hitTestResult;
if (event is PointerDownEvent ||
event is PointerSignalEvent ||
event is PointerHoverEvent ||
event is PointerPanZoomStartEvent) {
assert( // 判断 _hitTests 集合中是否已经存在
!_hitTests.containsKey(event.pointer),
'Pointer of ${event.toString(minLevel: DiagnosticLevel.debug)} unexpectedly has a HitTestResult associated with it.',
);
/// hitTestResult:用来存放执行命中测试后的结果,一般事件开始的时候都会先走这个
hitTestResult = HitTestResult();
/// 1、hitTestInView:执行命中测试相关的业务逻辑
hitTestInView(hitTestResult, event.position, event.viewId);
if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
/// 如果是 down 事件,则将命中测试的结果添加到集合 _hitTests 中
_hitTests[event.pointer] = hitTestResult;
}
.../// 省略代码
} else if (event is PointerUpEvent ||
event is PointerCancelEvent ||
event is PointerPanZoomEndEvent) {
/// 如果是抬起、取消等事件,则从 _hitTests 集合中移除,同时会返回当前的hitTestResult
/// 并执行其后续的事件分发,同时也代表这次事件流结束了
hitTestResult = _hitTests.remove(event.pointer);
} else if (event.down || event is PointerPanZoomUpdateEvent) {
/// 因为指针按下时发生的事件(如 [PointerMoveEvent]s)应该被分派到与它们初始 PointerDownEvent 所在的相同的位置
/// 我们希望重用指针按下时找到的路径 path,而不是在每次收到此类事件时都进行命中检测
hitTestResult = _hitTests[event.pointer];
}
.../// 省略代码
if (hitTestResult != null || event is PointerAddedEvent || event is PointerRemovedEvent) {
/// 3、hitTestResult 不为空,或事件是PointerAddedEvent、PointerRemovedEvent
/// 则继续执行 dispatchEvent 函数进行事件的分发
dispatchEvent(event, hitTestResult);
}
}
函数的执行流程如下:
- 调用 HitTestResult() 函数,先创建一个空的命中测试结果 hitTestResult ,其内部拥有一个 List<HitTestEntry> 类型的 _path 集合,用来存放执行命中测试后的结果集。
- 调用 hitTestInView() 函数,执行命中测试相关的业务逻辑,以此确定哪个 HitTestTarget 对象位于指定 View 中的给定位置。
- 如果是 down 事件,则将命中测试的结果添加到集合 _hitTests 中。如果是抬起、取消等事件,则从 _hitTests 集合中移除,并返回当前的 hitTestResult 。如果是 move 事件,则通过 event.pointer 获取到之前的 hitTestResult 对象,即集合 _hitTests 中保存的之前 PointerDownEvent 的时候保存下来的 hitTestResult 对象。
- 如果 hitTestResult 不为空,或事件是 PointerAddedEvent 、PointerRemovedEvent ,则执行 dispatchEvent 函数进行事件的分发。
二、命中测试
深度优先遍历整颗 RenderTree 即当前渲染树,判断当前事件的落点位置是否在 RenderObject 中,如果在范围内,就表示命中测试通过了,就会把自己封装成 HitTestEntry 添加到 HitTestResult 对象中。
1. HitTestResult
dart
/// hit_test.dart
/// 执行命中测试的结果
class HitTestResult {
/// 创建一个空的命中测试结果
HitTestResult()
: _path = <HitTestEntry>[], // 存放执行命中测试后的结果集
_transforms = <Matrix4>[Matrix4.identity()], // 矩阵变换
_localTransforms = <_TransformPart>[];
Iterable<HitTestEntry> get path => _path;
final List<HitTestEntry> _path;
}
HitTestResult 为执行命中测试的结果,成员 List<HitTestEntry> _path 用于存放执行命中测试后的结果集;
2. GestureBinding#hitTestInView
dart
/// gestures/binding.dart
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
/// 确定哪个 [HitTestTarget] 对象位于指定 view 中的给定位置
@override // from HitTestable
void hitTestInView(HitTestResult result, Offset position, int viewId) {
result.add(HitTestEntry(this));
}
}
hitTestInView 函数中,只是构建了一个 HitTestEntry 实例对象,并将其添加到 HitTestResult 中,每个 HitTestEntry.target 都会存储每个控件的 RenderObject。
流程追踪到这里没有看到想要的,那就看其子类里面的实现逻辑。通过关键字 mixin 和 on 的混入,RendererBinding 作为 GesturesBinding 的子类,继承并实现了 hitTestInView 函数,所以会先执行 RendererBinding#hitTestInView 函数。
3. RendererBinding#hitTestInView
dart
/// rendering/binding.dart
mixin RendererBinding on
BindingBase, GestureBinding, HitTestable {
/// 管理负责绘制的 RenderView
Iterable<RenderView> get renderViews => _viewIdToRenderView.values;
final Map<Object, RenderView> _viewIdToRenderView = <Object, RenderView>{};
@override
void hitTestInView(HitTestResult result, Offset position, int viewId) {
_viewIdToRenderView[viewId]?.hitTest(result, position: position);
/// 最后才会调用 GestureBinding 的 hitTestInView
super.hitTestInView(result, position, viewId);
}
}
hitTestInView 函数根据传入的 viewId 获取对应的 RenderView ,并调用其 hitTest 函数。执行完 RenderView 的 hitTest 函数后再继续调用 super.hitTestInView(result, position, viewId); 也就是执行 GestureBinding 的 hitTestInView 函数将自身添加到 HitTestResult 中。
需要注意 :GestureBinding 最后把自己添加到 HitTestResult 的尾部。
4. RenderView#hitTest
dart
/// view.dart
/// 渲染树的根,负责启动渲染管道,其有一个唯一的子节点[RenderBox],用来填充整个输出页面
class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
RenderView({RenderBox? child, ViewConfiguration? configuration, required ui.FlutterView view})
: _view = view {
if (configuration != null) {
this.configuration = configuration;
}
this.child = child;
}
bool hitTest(HitTestResult result, {required Offset position}) {
/// 这里的 child 其实是 RenderBox,当 child 不为空,触发子节点 RenderBox 的 hitTest
child?.hitTest(BoxHitTestResult.wrap(result), position: position);
/// 最后将自身即根 RenderView 加入命中测试结果
result.add(HitTestEntry(this));
return true;
}
}
RenderView 是绘制树 RenderObject 的根节点,是所有 Widget 的祖先,从这里开始,会遍历整颗 RenderTree 执行 histTest 。
RenderView 有一个唯一的子节点 RenderBox ,用来填充整个输出页面。因此 RenderView 的 hitTest 函数最终调用的是子节点 RenderBox 的 hitTest 函数。
5. RenderBox#hitTest
dart
/// box.dart
abstract class RenderBox extends RenderObject {
/// 确定位于给定 position 的渲染对象的集合
/// 返回 true,即当前节点或他的子节点位于给定的 position,将其添加到给定的命中测试结果集中
/// 返回 false,表示当前节点或其子孙节点都未命中,继续交给当前节点之后的节点对象处理
bool hitTest(BoxHitTestResult result, {required Offset position}) {
.../// 省略代码
if (_size!.contains(position)) { /// 判断自己是否在这次点击的 position 范围内
/// 优先判断 children,再判断自己,只要有一个为true,就把自己加入到result中
/// 如果孩子和自己都符合条件,那孩子是先加入到队列里的
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
/// 通过了命中测试,即在点击范围内,则添加到 result 中
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
/// 决定自身是否通过命中测试,如果节点需要确保自身一定能响应事件可以重写此函数并返回 true,相当于"强行声明"自己通过了命中测试
@protected
bool hitTestSelf(Offset position) => false; /// 默认返回 false
/// 判断是否有子节点通过了命中测试,如果有,则会将子组件添加到 HitTestResult 中同时返回 true;如果没有则直接返回 false
@protected
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) => false;
}
RenderBox 的 hitTest 函数的整体逻辑如下:
- 先判断事件的触发位置是否位于组件 position 范围内,如果不是则不会通过命中测试,此时 hitTest 返回 false,如果是则继续下一步。
- 先调用 hitTestChildren 判断是否有子节点通过命中测试,如果是则将当前节点封装成 BoxHitTestEntry 加入到 HitTestResult 列表,此时 hitTest 返回 true。即只要有子节点通过了命中测试,那么它的父节点(当前节点)也会通过命中测试。
- 如果没有子节点通过命中测试,则会取 hitTestSelf 方法的返回值,如果返回值为 true,则当前节点通过命中测试,反之则否。
注意: 如果父子节点都监听了同一个事件,则子节点会比父节点先响应事件。这是因为命中测试过程是按照深度优先规则遍历的,所以子节点会比父节点先加入 HitTestResult 列表,又因为在事件分发时是从前到后遍历 HitTestResult 列表的,所以子节点比父节点会更先被调用 handleEvent。
BoxHitTestResult 是 HitTestResult 的子类,BoxHitTestResult 没有重写 HitTestResult#add 函数,所以这里调用的是 HitTestResult 的 add 函数,将触点信息封装成 BoxHitTestEntry ,然后添加到 _path 集合中。
BoxHitTestEntry 是 HitTestEntry 的子类,相比父类增加了 localPosition 属性,表示命中测试在 target 局部坐标中的位置。
hitTestChildren 和 hitTestSelf 是两个抽象方法,具体由子类实现。而子类分两种:一种是单子节点的,一种是多子节点的,分别挑一个看下代码的具体实现。
5.1 RenderShiftedBox#hitTestChildren
单子节点 的组件,以 Padding 为例,Padding 对应的 RenderObject 是 RenderPadding ,其父类是RenderShiftedBox ,RenderShiftedBox 继承 RenderBox 并实现了 hitTestChildren 函数,那就看下 RenderShiftedBox 的 hitTestChildren 函数实现:
dart
/// shifted_box.dart
abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
final RenderBox? child = this.child;
if (child != null) {
final BoxParentData childParentData = child.parentData! as BoxParentData;
/// 根据偏移量计算,实际上就是根据 offset 做个偏移,然后调用孩子节点 RenderBox 的 hitTest
return result.addWithPaintOffset(
offset: childParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - childParentData.offset);
return child.hitTest(result, position: transformed); /// 返回是否命中
},
);
}
return false;
}
}
hitTestChildren 函数,首先判断该节点是否还有子节点,如有则调用执行 BoxHitTestResult 的 addWithPaintOffset 函数,其内部会根据是否有偏移量 Offset ,然后通过计算做个偏移,最后调用子节点 RenderBox 的 hitTest 函数执行子节点的命中测试,并继续上面的步骤,单子节点比较容易看懂,只要还有子节点并且触点信息包含在 Widget 范围之内就一层一层的迭代下去。
5.2 RenderShiftedBox#hitTestChildren
多子节点 的容器类组件,如:Row 和 Column ,Row 和 Column 的父类都是 Flex ,Flex 对应的 RenderObject 是 RenderFlex ,RenderFlex 继承 RenderBox 并实现了 hitTestChildren 函数,看下函数实现:
dart
/// flex.dart
class RenderFlex extends RenderBox with
RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData> {
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return defaultHitTestChildren(result, position: position);
}
}
RenderFlex 的 hitTestChildren 函数实现也很简单,调用 defaultHitTestChildren 函数并返回。由于 RenderFlex 混入了 RenderBoxContainerDefaultsMixin 类,而 RenderBoxContainerDefaultsMixin 是用关键字 mixin 定义的一个混入类,其 defaultHitTestChildren 函数实现了 hitTestChildren 的逻辑,同时由于 Flex 里可以有多个孩子,所以
会循环调用 hitTest 函数。
5.3 RenderBoxContainerDefaultsMixin#defaultHitTestChildren
dart
/// box.dart
mixin RenderBoxContainerDefaultsMixin<
ChildType extends RenderBox,
ParentDataType extends ContainerBoxParentData<ChildType>>
implements ContainerRenderObjectMixin<ChildType, ParentDataType> {
bool defaultHitTestChildren(BoxHitTestResult result, {required Offset position}) {
ChildType? child = lastChild; /// 子节点集合中的最后一个节点,从后向前遍历所有子节点
while (child != null) { /// 节点不为 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( /// isHit 为当前子节点调用hitTest() 的返回值
offset: childParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) { /// 调用子节点的 hitTest 函数
assert(transformed == position - childParentData.offset);
return child!.hitTest(result, position: transformed);
},
);
if (isHit) { /// 一旦有子节点命中,则终止遍历,直接返回 true
return true;
}
/// 如果没有命中,就往前取,继续遍历
child = childParentData.previousSibling;
}
return false;
}
}
RenderBoxContainerDefaultsMixin 的 defaultHitTestChildren 函数内,倒序获取子节点集合中的子节点,并执行每个子节点的 hitTest 函数。一旦有子节点命中,则终止循环返回 true ,然后把自己加入到 HitTestResult 中。
注意 :在多节点的情况下只要有一个子节点响应命中测试那么就会返回 true ,其它子节点就不会在遍历,并且会继续迭代找到的响应触点的控件。
6. HitTestResult#add
dart
/// hit_test.dart
class HitTestResult {
Iterable<HitTestEntry> get path => _path;
final List<HitTestEntry> _path;
/// 新的 HitTestEntry 添加到 _path 集合的尾部
void add(HitTestEntry entry) {
assert(entry._transform == null);
entry._transform = _lastTransform;
_path.add(entry);
}
}
深度优先遍历整颗 RenderTree ,判断当前事件的落点位置是否在 RenderObject 中,如果在范围内,则表示命中测试通过,然后把自己封装成 HitTestEntry 添加到 HitTestResult 对象的 _path 集合中。
整颗 RenderTree 遍历完后,hitTestResult 对象的 _path 集合应该如下图:

总结 :命中测试 RenderObjcet#hitTest 函数的源码,通过 _size!.contains(position) 函数判断自己是否属于响应区域,确认属于响应区域后,执行 hitTestChildren 函数,只要还有子节点并且触点信息包含在 Widget 范围之内就一层层的递归调用 子节点,并重复执行上述命中测试逻辑,最终得到并维护一个 HitTestResult#_path 集合,并且层级越深的子节点在集合的最前面。递归调用 结束,最后再执行 GestureBinding#hitTest 函数把自己也添加到 HitTestResult#_path 集合的尾部,这是因为后面的流程还会需要回到 GestureBinding 中去处理。
三、事件分发
1. GestureBinding#dispatchEvent
分析完事件传递 和命中测试 ,再回到 6. GestureBinding#_handlePointerEventImmediately 函数的最后,如果 hitTestResult 集合不为空则执行 GestureBinding#dispatchEvent 函数进行事件的分发:
dart
/// gestures/binding.dart
@override // from HitTestDispatcher
@pragma('vm:notify-debugger-on-exception')
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
assert(!locked);
/// 如果命中测试结果为 null,则通过 pointerRouter.route 将事件分发到全局处理
if (hitTestResult == null) {
/// hitTestResult 为 null 说明是可能回调了
/// PointerHoverEvent,PointerAddedEvent,PointerRemovedEvent 由路由分发出去
assert(event is PointerAddedEvent || event is PointerRemovedEvent);
try {
/// 将事件分发到注册了此次事件的路由,一般是由
/// GestureRecognizer 中 void addPointer(PointerDownEvent event) 方法进行注册
pointerRouter.route(event);
} catch (exception, stack) {
.../// 省略代码
}
return;
}
for (final HitTestEntry entry in hitTestResult.path) {
try {
/// 执行命中测试添加进来的每个 HitTestTarget 的 handleEvent 函数
entry.target.handleEvent(event.transformed(entry.transform), entry);
} catch (exception, stack) {
.../// 省略代码
}
}
}
如果命中测试结果 hitTestResult 为 null ,则通过 pointerRouter#route 将事件分发到全局处理。否者遍历 hitTestResult.path 集合,执行每一个 HitTestEntry 的 HitTestTarget 对象的 handleEvent 函数处理事件。
那 HitTestEntry 的 HitTestTarget 对象指向的是谁呢?先来看一下 HitTestEntry 类
2. HitTestEntry
dart
/// hit_test.dart
/// 命中测试期间保存的关于特定 HitTestTarget 的数据
/// 创建该对象的子类,用来将命中测试阶段的附加信息传递到事件分发阶段
@optionalTypeArgs
class HitTestEntry<T extends HitTestTarget> {
HitTestEntry(this.target); // 创建一个命中测试条目
/// 保存命中测试过程中遇到的 HitTestTarget
final T target;
@override
String toString() => '${describeIdentity(this)}($target)';
Matrix4? get transform => _transform;
Matrix4? _transform;
}
/// box.dart
/// RenderBox使用的一个命中测试条目
class BoxHitTestEntry extends HitTestEntry<RenderBox> {
/// 创建一个 box 命中测试条目
BoxHitTestEntry(super.target, this.localPosition);
/// The position of the hit test in the local coordinates of [target].
final Offset localPosition;
@override
String toString() => '${describeIdentity(target)}@$localPosition';
}
由前面的代码分析可知,每次命中测试的时候,符合条件的节点会封装一些信息到 HitTestEntry 或 BoxHitTestEntry 中,然后添加到 hitTestResult 中保存。观察构建 HitTestEntry 和 BoxHitTestEntry 时传入的参数都有 this 自身,即 HitTestEntry 实例对象的 target 保存的是实现 HitTestTarget 接口的当前控件的 RenderObject (RenderObject 默认都实现了 HitTestTarget 接口,因此 HitTestEntry 的 HitTestTarget 大部分时候都是 RenderObject)。
注意 :并不是所有控件的 RenderObject 类都会实现 handleEvent 函数来处理事件。通过跟踪代码并分析,可知 entry.target.handleEvent 最后是由 RenderPointerListener 实现的 handleEvent 函数来处理事件的。RenderPointerListener 是 Listener (可以监听原始 PointEvent 的 Widget )控件对应的 RenderObject 对象,该类间接继承了 HitTestTarget 接口。
3. RenderPointerListener#handleEvent
dart
class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
RenderPointerListener({
this.onPointerDown,
this.onPointerMove,
this.onPointerUp,
this.onPointerHover,
this.onPointerCancel,
this.onPointerPanZoomStart,
this.onPointerPanZoomUpdate,
this.onPointerPanZoomEnd,
this.onPointerSignal,
super.behavior,
super.child,
});
PointerDownEventListener? onPointerDown; /// 监听 PointerDownEvent 事件
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
assert(debugHandleEvent(event, entry));
return switch (event) {
PointerDownEvent() => onPointerDown?.call(event),
PointerMoveEvent() => onPointerMove?.call(event),
PointerUpEvent() => onPointerUp?.call(event),
PointerHoverEvent() => onPointerHover?.call(event),
PointerCancelEvent() => onPointerCancel?.call(event),
PointerPanZoomStartEvent() => onPointerPanZoomStart?.call(event),
PointerPanZoomUpdateEvent() => onPointerPanZoomUpdate?.call(event),
PointerPanZoomEndEvent() => onPointerPanZoomEnd?.call(event),
PointerSignalEvent() => onPointerSignal?.call(event),
_ => null,
};
}
}
函数内部会判断当前是什么类型的事件,然后执行与之对应的回调函数,如 down 事件则执行 onPointerDown 函数。那么这些回调函数是在哪里设置的呢?
四、事件监听
Flutter 的官方文档推荐使用 GestureDetector 控件来监听并处理单击、双击、长按以及滑动等,通过其内部封装好的手势识别器来检测区别各种类型的手势,开发时只需要设置需要监听的手势回调即可,使用非常方便。

接下来根据上图中调用时序,深入源码分析 Flutter 的事件监听、手势竞争及响应。
1. GestureDetector
GestureDetector 本身是一个 StatelessWidget ,内部封装了日常开发需要的 8 大手势识别器。
dart
/// gesture_detector.dart
class GestureDetector extends StatelessWidget {
@override
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{}; /// 手势识别器工厂集合
final DeviceGestureSettings? gestureSettings = MediaQuery.maybeGestureSettingsOf(context);
final ScrollBehavior configuration = ScrollConfiguration.of(context);
if (onTapDown != null || /// 单击事件手势识别器
onTapUp != null || onTap != null || onTapCancel != null ||
onSecondaryTap != null ||...onTertiaryTapDown != null ||...) {
/// 构建出泛型为 TapGestureRecognizer 的 GestureRecognizerFactoryWithHandlers 对象
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
/// 代码1 -- 返回对应类型的手势识别器
() => TapGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
/// 代码2 -- 通过该对象的回调对 GestureDetector 中定义的回调进行绑定,
/// GestureDetector 中的回调方法,本质上都是在 手势识别器 中触发的
(TapGestureRecognizer instance) {
instance
..onTapDown = onTapDown
..onTapUp = onTapUp
..onTap = onTap
..onTapCancel = onTapCancel
..onSecondaryTap = onSecondaryTap
.../// 省略部分代码
..supportedDevices = supportedDevices;
},
);
}
/// 双击事件手势识别器
if (onDoubleTap != null || onDoubleTapDown != null || onDoubleTapCancel != null) {
gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(...);
}
/// 长按事件手势识别器
if (onLongPressDown != null ||...onSecondaryLongPressDown != null ||...onTertiaryLongPressDown != null ||...) {
gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(...);
}
/// 垂直滑动事件手势识别器
if (onVerticalDragDown != null ||...onVerticalDragCancel != null) {
gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(...);
}
/// 水平滑动事件手势识别器
if (onHorizontalDragDown != null ||...onHorizontalDragCancel != null) {
gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(...);
}
/// 水平和垂直滑动事件手势识别器
if (onPanDown != null ||...onPanCancel != null) {
gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(...);
}
/// 缩放事件手势识别器
if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(...);
}
/// 压力传感器事件手势识别器
if (onForcePressStart != null ||...onForcePressEnd != null) {
gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(...);
}
/// GestureDetector 内部封装了8大手势识别器,其实已经可以满足绝大部分的使用了,但
/// 它的功能也仅仅只限与此了,GestureDetector的具体实现最终还是委托给 RawGestureDetector 来实现
/// 对于 RawGestureDetector 组件的使用而言,最关键的就是处理 Map<Type, GestureRecognizerFactory> 这个手势识别器工厂映射。
return RawGestureDetector(
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
}
}
GestureDetector 的 build 函数,将手势识别器工厂对象保存到 gestures 中,注意这里的 Key 的类型为具体的手势识别器,Value 是生产对应手势识别器的 GestureRecognizerFactoryWithHandlers 类对象,其继承自 GestureRecognizerFactory 抽象类,是具体的手势识别器工厂类,重写了 GestureRecognizerFactory 类的 constructor 和 initializer 函数,但是没有写具体的逻辑,而是把逻辑处理交给了 _constructor() 和 _initializer(instance) 函数。
以单击事件 来说,构建时传入的泛型类型是 TapGestureRecognizer 说明这个工厂类最终创建的是一个单击手势识别器,在代码1 处执行的就是 GestureRecognizerFactoryWithHandlers 的 _constructor 函数用于生成手势识别器对象。代码2 处执行的是 _initializer(instance) 函数,用于执行回调绑定的操作,到这里手势识别器其实已经实现了手势回调,比如说 onTap 的回调最终就是交给 TapGestureRecognizer 来处理的,手势识别器工厂的作用就是创建手势识别器 以及回调绑定。
GestureDetector 的具体实现最终是委托给其返回的 RawGestureDetector 来实现,对于 RawGestureDetector 组件的实现而言,其核心就是处理 Map<Type, GestureRecognizerFactory> gestures 这个手势识别器工厂集合。
2. RawGestureDetector
dart
/// gesture_detector.dart
class RawGestureDetector extends StatefulWidget {
const RawGestureDetector({
super.key,
this.child,
this.gestures = const <Type, GestureRecognizerFactory>{},
this.behavior,
this.excludeFromSemantics = false,
this.semantics,
});
final Map<Type, GestureRecognizerFactory> gestures;
@override
RawGestureDetectorState createState() => RawGestureDetectorState();
}
/// State for a [RawGestureDetector].
class RawGestureDetectorState extends State<RawGestureDetector> {
/// 初始化一个 _recognizers map 集合
Map<Type, GestureRecognizer>? _recognizers = const <Type, GestureRecognizer>{};
SemanticsGestureDelegate? _semantics;
@protected
@override
void initState() {
super.initState();
_semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
/// _syncAll 函数对 gestures 集合做处理
_syncAll(widget.gestures);
}
@protected
@override
void didUpdateWidget(RawGestureDetector oldWidget) {
super.didUpdateWidget(oldWidget);
if (!(oldWidget.semantics == null && widget.semantics == null)) {
_semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
}
/// 当外界调用 setState 而触发本状态的 didUpdateWidget 时,也会进行 _syncAll 中的逻辑
_syncAll(widget.gestures);
}
@protected
@override
void dispose() {
for (final GestureRecognizer recognizer in _recognizers!.values) {
recognizer.dispose(); /// 销毁 _recognizers 中的所有的手势检测器
}
_recognizers = null;
super.dispose();
}
void _syncAll(Map<Type, GestureRecognizerFactory> gestures) { /// 同步所有的手势检测器
assert(_recognizers != null);
/// 1、将状态类中维护的 _recognizers 保存为局部变量 oldRecognizers,而 _recognizers
/// 在第一次使用的时候是一个空集合,所以此刻 oldRecognizers 也是一个空集合
final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
/// 重新初始化 _recognizers 成员,为空映射 {}
_recognizers = <Type, GestureRecognizer>{};
for (final Type type in gestures.keys) { /// 遍历传入的 gestures,为 _recognizers 中添加成员
.../// 省略断言代码
/// 获取并调用 gestures 保存的手势识别器工厂对象的 constructor 函数返回手势识别器填充到 _recognizers中
/// 如果 oldRecognizers 已经存在对应类型的 GestureRecognizer,就会直接复用,而不是创建新的
_recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
assert(
_recognizers![type].runtimeType == type,
.../// 省略断言代码
);
/// 调用手势识别器工厂对象的 initializer 函数绑定监听回调
gestures[type]!.initializer(_recognizers![type]!);
}
/// 如果 oldRecognizers 中有_recognizers 中不存在的类型,则将其销毁
for (final Type type in oldRecognizers.keys) {
if (!_recognizers!.containsKey(type)) {
oldRecognizers[type]!.dispose();
}
}
}
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
/// 遍历 _recognizers 中的 手势识别器 对象,并为其添加 PointerDownEvent
/// 手势事件数据 PointerDownEvent 通过 Listener 组件的 onPointerDown 回调过来
for (final GestureRecognizer recognizer in _recognizers!.values) {
recognizer.addPointer(event);
}
}
@protected
@override
Widget build(BuildContext context) {
/// RawGestureDetector 的核心最终还是 Listener 组件
/// 手势识别器 GestureRecognizer 是和 RawGestureDetector 相关,和 Listener 组件没有直接关系
/// 在你点击按钮的时候会触发 onPointerDown 回调,调用 _handlePointerDown 方法
Widget result = Listener(
onPointerDown: _handlePointerDown,
onPointerPanZoomStart: _handlePointerPanZoomStart,
behavior: widget.behavior ?? _defaultBehavior,
child: widget.child,
);
if (!widget.excludeFromSemantics) {
result = _GestureSemantics(
behavior: widget.behavior ?? _defaultBehavior,
assignSemantics: _updateSemanticsForRenderObject,
child: result,
);
}
return result;
}
}
RawGestureDetector 在初始化和状态发生改变时,执行 _syncAll 函数对 gestures 集合内的所有手势识别器做同步处理。
- 遍历传入的 gestures 集合,获取并调用 gestures 保存的手势识别器工厂对象的 constructor 函数返回手势识别器填充到 _recognizers 集合中。如果已存在对应类型的 GestureRecognizer,则直接复用而不是创建新的。
- 调用手势识别器工厂对象的 initializer 函数绑定监听回调。
- RawGestureDetector 的 build 函数返回的是一个 Listener 组件,RenderPointerListener 是 Listener (可以监听原始 PointEvent 的 Widget )对应的 RenderObject 对象。
3. Listener
dart
class Listener extends SingleChildRenderObjectWidget {
const Listener({
super.key,
this.onPointerDown,
.../// 省略代码
this.behavior = HitTestBehavior.deferToChild,
super.child,
});
/// RawGestureDetector 构建 Listener 组件时传入的 _handlePointerDown 函数
final PointerDownEventListener? onPointerDown;
/// 在命中测试期间的行为表现
final HitTestBehavior behavior;
@override
RenderPointerListener createRenderObject(BuildContext context) {
return RenderPointerListener(
onPointerDown: onPointerDown, /// down 事件的回调
.../// 省略代码
behavior: behavior,
);
}
}
前文分析过,GestureBinding#dispatchEvent 函数进行事件分发时,执行的 entry.target.handleEvent 函数是 Listener 组件创建的 RenderPointerListener#handleEvent 函数。该函数根据不同的事件类型触发 Listener 组件对应的回调函数,以点击事件来说,从按下 down 事件开始,到 up 事件结束。首先是 down 事件触发 Listener 的 onPointerDown 回调函数,该事件最终会被交给 RawGestureDetector#_handlePointerDown 函数处理,通过遍历 _recognizers 中保存的手势识别器对象,并执行 GestureRecognizer#addPointer 函数为每个手势识别器添加 PointerDownEvent 事件。
4. GestureRecognizer#addPointer
dart
/// recognizer.dart
abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableTreeMixin {
void addPointer(PointerDownEvent event) {
_pointerToKind[event.pointer] = event.kind;
/// 检查是否允许此手势识别器跟踪指针
if (isPointerAllowed(event)) {
/// 如果允许,则注册一个新指针,该指针允许此手势识别器使用
addAllowedPointer(event);
} else {
handleNonAllowedPointer(event);
}
}
/// [GestureRecognizer] 的子类应该覆盖此方法而不是 [addPointer]
/// 因为 [addPointer] 为每个被添加的指针调用,而 [addAllowedPointer] 仅对该识别器允许的指针调用
@protected
void addAllowedPointer(PointerDownEvent event) {}
}
GestureRecognizer 是一个抽象类,一种手势的识别器对应一个 GestureRecognizer 的子类,其作用就是通过 Listener 来将原始指针事件转换为语义手势。GestureRecognizer#addPointer 函数首先检查是否允许此手势识别器跟踪指针,如果允许,则通过 addAllowedPointer 函数注册一个新指针,该指针允许此手势识别器使用。由 GestureRecognizer 的子类来实现该函数,目前我们先只关注 TapGestureRecognizer 类,TapGestureRecognizer 继承自 BaseTapGestureRecognizer 但没有重写该函数,而 BaseTapGestureRecognizer 类重写了 addAllowedPointer 函数,其内部做了校验和赋值后,继续调用了其父类 PrimaryPointerGestureRecognizer 的 addAllowedPointer 函数,其内部继续调用父类 OneSequenceGestureRecognizer 的 addAllowedPointer 函数,之后判断并更新手势识别器的状态信息等。
5. OneSequenceGestureRecognizer#addAllowedPointer
dart
/// recognizer.dart
abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{};
final Set<int> _trackedPointers = HashSet<int>();
@override
@protected
void addAllowedPointer(PointerDownEvent event) {
/// 将与给定指针 ID 相关的事件路由到此识别器
startTrackingPointer(event.pointer, event.transform);
}
GestureArenaTeam? get team => _team;
GestureArenaTeam? _team;
/// team 只能设置一次
set team(GestureArenaTeam? value) {
assert(value != null);
assert(_entries.isEmpty);
assert(_trackedPointers.isEmpty);
assert(_team == null);
_team = value;
}
/// 如果 _team 不为空,则此手势识别器将与同一 _team 的其他识别器一起在竞技场中竞争
/// 如果 _team 为空,则此手势识别器将直接添加到竞技场中竞争
GestureArenaEntry _addPointerToArena(int pointer) {
/// pointer:触点id,this:手势识别器,这里指单击手势识别器
return _team?.add(pointer, this) ?? GestureBinding.instance.gestureArena.add(pointer, this);
}
@protected
void startTrackingPointer(int pointer, [Matrix4? transform]) {
/// 将 handleEvent 作为参数传入触点路由对象,指针事件根据 transform 进行转换,然后传给 handleEvent
/// transform 用于将事件从全局坐标空间转换到事件接收者的坐标空间,如不需转换,则传空
GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);
_trackedPointers.add(pointer); /// _trackedPointers 是一个 HashSet,记录此 Recognizer 追踪的事件序列
/// 执行 _addPointerToArena 函数实质上就是将自己添加到手势竞技场中
_entries[pointer] = _addPointerToArena(pointer);
}
}
OneSequenceGestureRecognizer#addAllowedPointer 函数继续调用 startTrackingPointer 函数将与给定手势 ID 相关的事件路由到此识别器,该 Recognizer 就可以接受处理余下的事件序列,当有余下事件序列发送过来就会调用 Recognizer#handleEvent 函数。
OneSequenceGestureRecognizer#startTrackingPointer 函数,首先获取 GestureBinding 中维护的触点路由对象 pointerRouter ,执行其 addRoute 函数并将 handleEvent 作为参数传给触点路由对象保存。随后执行 _addPointerToArena 函数,其内部会判断 _team 是否为空,由于我们只是追踪单一的点击事件,因此只看 _team 为空的逻辑,即通过 GestureBinding 中实例化的竞技场管理器 GestureArenaManager#add 函数将自己添加到竞技场中。
6. GestureArenaManager#add
dart
/// arena.dart
class GestureArenaManager {
final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
/// 手势竞技场中添加一下新的竞争成员
GestureArenaEntry add(int pointer, GestureArenaMember member) {
/// putIfAbsent 是 Map 类的一个方法,第2个参数是一个返回 value 值的函数对象
/// 且只有当 key 不存在,才会添加该映射元素。该方法最终会返回 key 对应的 value
final _GestureArena state = _arenas.putIfAbsent(pointer, () {
assert(_debugLogDiagnostic(pointer, '★ Opening new gesture arena.'));
return _GestureArena();
});
state.add(member); /// 将 GestureArenaMember 添加到 _GestureArena 竞技场中
assert(_debugLogDiagnostic(pointer, 'Adding: $member'));
/// 创建并返回 GestureArenaEntry 实例,封装参与事件竞争的成员
return GestureArenaEntry._(this, pointer, member);
}
}
GestureArenaManager#add 函数,新建一个竞技场 _GestureArena ,并将竞争者 GestureArenaMember 添加到竞技场的 List<GestureArenaMember> members 集合中,最后创建并返回 GestureArenaEntry 实例,封装参与事件竞争的成员。
回到 OneSequenceGestureRecognizer#startTrackingPointer 函数的最后,GestureArenaEntry 实例是添加到 OneSequenceGestureRecognizer 的 Map<int, GestureArenaEntry> _entries 集合中,后续由手势识别器根据设定的处理方式来裁决手势。
注意 :一个 GestureArenaMember 可以在不同 _GestureArena 中有不同的 GestureArenaEntry ,这些不同GestureArenaEntry 是根据 pointer 来区分的。
7. _GestureArena#add
dart
/// arena.dart
class _GestureArena {
final List<GestureArenaMember> members = <GestureArenaMember>[];
bool isOpen = true;
bool isHeld = false;
bool hasPendingSweep = false;
/// 如果一名成员在竞技场开放期间试图获胜,则该成员成为"渴望获胜者"。在关闭竞技场、
/// 停止接受新竞争参与者时会寻找渴望获胜者,如果找到,则立即判定该成员获胜。
GestureArenaMember? eagerWinner;
void add(GestureArenaMember member) {
assert(isOpen);
members.add(member); /// 新竞争者加入到竞技场中
}
@override
String toString() {
final StringBuffer buffer = StringBuffer();
.../// 省略代码
}
}
小结
GestureBinding 分发事件时所有通过命中测试的组件都会触发RenderPointerListener#handleEvent 函数,并且事件流程中第一个事件一般都是 PointerDownEvent 。因此根据事件类型回调监听事件的组件 RawGestureDetector#_handlePointerDown 函数,其通过循环遍历 GestureDetector 内保存的每个手势识别器 GestureRecognizer ,调用 GestureRecognizer#addPointer 函数将 PointerDownEvent 事件和自己绑定,并添加到 GestureBinding 的 PointerRouter 事件路由和 _GestureArena 手势竞技场中,后续事件该组件的 GestureRecognizer 才能响应和参与竞争。
五、手势竞争及响应
Flutter 在设计手势竞争的时候,定义了一个很有趣的概念:通过一个竞技场,各个控件参与竞争,直接胜利的或者活到最后的第一位,就获胜得到了胜利。 那么为了分析接下来的手势竞争,我们需要先看几个概念:
- GestureRecognizer :手势识别器基类,基本上 RenderPointerListener 中需要处理的手势事件,都会分发到它对应的 GestureRecognizer ,并经过它处理和竞争后再分发出去,常见有:OneSequenceGestureRecognizer 、MultiTapGestureRecognizer 、VerticalDragGestureRecognizer 、TapGestureRecognizer 等。
- GestureArenaManager:手势竞技管理,其管理了整个竞争的过程,原则上竞争胜出的条件是:第一个竞争获胜的成员或最后一个不被拒绝的成员。
- GestureArenaEntry:提供手势事件竞争信息的实体,内封装参与事件竞争的成员。
- GestureArenaMember :参与手势竞争的成员抽象对象,内部有 acceptGesture 和 rejectGesture 方法,代表手势竞争的成员,默认 GestureRecognizer 都实现了它,所有竞争的成员可以理解为就是 GestureRecognizer 之间的竞争。
- _GestureArena :GestureArenaManager 内的竞技场,内部持参与竞技的 members 成员列表,官方对这个竞技场的解释是: 如果一个手势试图在竞技场开放时 (isOpen=true) 获胜,它将成为一个带有"渴望获胜 "属性的对象。当竞技场关闭 (isOpen=false) 时,竞技场将寻找一个"渴望获胜"的对象成为新的参与者,如果这时候刚好只有一个,那这一个参与者将成为这次竞技场胜利的存在。
也就是,GestureBinding#dispatchEvent 分发事件的时候,当有多个控件可以响应事件时,这些 GestureRecognizer 会作为一个个 GestureArenaMember 添加到 _GestureArena 竞技场中进行竞争。那后续怎么竞争呢?
前面在分析命中测试的时候,GestureBinding 也通过了命中测试,GestureBinding#hitTest 函数把自己也添加到 HitTestResult#_path 集合的尾部,即 GestureBinding#handleEvent 函数也会被调用,且由于他是最后被添加到 HitTestResult 中的,所以在事件分发阶段 GestureBinding#handleEvent 函数会在最后被调用,那手势竞争是不是由他处理呢?
1. GestureBinding#handleEvent
dart
/// gestures/binding.dart
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
/// 指针事件路由器--路由从 engine 层接收的所有指针事件
final PointerRouter pointerRouter = PointerRouter();
/// 手势竞技管理--管理多个手势的冲突和优先级,用于处理指针事件序列
final GestureArenaManager gestureArena = GestureArenaManager();
/// 指针信号解析器--用于确定由哪个 Widget 处理 PointerSignalEvent
final PointerSignalResolver pointerSignalResolver = PointerSignalResolver();
@override // from HitTestTarget -- 由 HitTestTarget 调用
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event); /// 调用 PointerRouter 的 route 方法将事件进行路由
/// PointerDownEvent-- 手指按下事件 PointerPanZoomStartEvent-- 已开始对该指针进行平移/缩放
if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
gestureArena.close(event.pointer); /// 关闭竞技场,阻止其他 Recognizer 加入到手势竞技中
} else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
gestureArena.sweep(event.pointer); /// Up 事件时,竞技场开始清扫
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
}
对于 PointerDownEvent 或 PointerPanZoomStartEvent 事件,调用 GestureArenaManager#close 方法关闭竞技场,因为此时参加手势竞争的控件 (竞争参与者 ) 已经确定了;对于 PointerUpEvent 或 PointerPanZoomEndEvent 事件,则调用 GestureArenaManager#sweep 方法来解决竞技场上的冲突。
2. GestureArenaManager#close
dart
/// arena.dart
class GestureArenaManager {
final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
/// 阻止新成员进入竞技场 -- 在 framework 完成指针按下事件的分发后调用,即在分发完 down 事件之后
void close(int pointer) {
/// 根据事件 id 获取前面 GestureRecognizer#addPointer 时添加的成员 _GestureArena 对象
final _GestureArena? state = _arenas[pointer];
if (state == null) { /// 为空,表示竞技场要么从未存在过,要么就是冲突已经解决
return; // 直接返回
}
state.isOpen = false; /// 关闭竞技场
assert(_debugLogDiagnostic(pointer, 'Closing', state));
/// 尝试解决竞技场中的冲突
_tryToResolveArena(pointer, state);
}
void _tryToResolveArena(int pointer, _GestureArena state) {
assert(_arenas[pointer] == state);
assert(!state.isOpen);
if (state.members.length == 1) {
/// 如果只有一个竞争成员,则直接交给它处理--直接添加一个 _resolveByDefault 方法调用的 task
/// 后续的 Move,Up 事件都不用走了
scheduleMicrotask(() => _resolveByDefault(pointer, state));
} else if (state.members.isEmpty) {
_arenas.remove(pointer); /// 如果没有竞争成员,则直接移除该 pointer 对应的 _GestureArena
assert(_debugLogDiagnostic(pointer, 'Arena empty.'));
} else if (state.eagerWinner != null) {
assert(_debugLogDiagnostic(pointer, 'Eager winner: ${state.eagerWinner}'));
/// 如果竞争成员不唯一,且 eagerWinner 即渴望获胜者不为空,则交给 eagerWinner 处理
_resolveInFavorOf(pointer, state, state.eagerWinner!);
}
}
void _resolveByDefault(int pointer, _GestureArena state) {
if (!_arenas.containsKey(pointer)) { /// 判断当前 pointer 对应的竞技场是否存在,不存在则意味着冲突已经解决
return; // This arena has already resolved.
}
assert(_arenas[pointer] == state);
assert(!state.isOpen);
final List<GestureArenaMember> members = state.members;
assert(members.length == 1);
_arenas.remove(pointer); // 移除,获取竞争胜利了,就不用存在了
assert(_debugLogDiagnostic(pointer, 'Default winner: ${state.members.first}'));
state.members.first.acceptGesture(pointer); /// 调用胜利者的 acceptGesture 接受手势
}
/// 宣布指定的竞争者胜利
void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
assert(state == _arenas[pointer]);
assert(state.eagerWinner == null || state.eagerWinner == member);
assert(!state.isOpen);
_arenas.remove(pointer); /// 移除竞技场,已经得出结果不需要了
for (final GestureArenaMember rejectedMember in state.members) {
if (rejectedMember != member) { /// 将不是胜利者的 GestureArenaMember 全部调用拒绝手势
rejectedMember.rejectGesture(pointer);
}
}
member.acceptGesture(pointer); /// 调用胜利者的接受手势,宣告这个竞技者获胜
}
}
分析上述代码可知,手指按下或已经开始移动/缩放后,手势竞技场关闭,此时如果只有一个竞争成员的情况下这个控件直接获得胜利,触发其 acceptGesture 流程;如果控件区域内存在多个竞争者 (如:TapGestureRecognizer ),那么在 PointerDownEvent 或 PointerPanZoomStartEvent 时,如果 _GestureArena#eagerWinner 即渴望获胜者不为空,则交给它来处理,否者在此阶段是不会产生胜利者的。
3. GestureArenaManager#sweep
dart
/// arena.dart
class GestureArenaManager {
final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
/// 强制解决竞技场上的冲突,迫使竞技场得出一个决胜者
/// sweep 通常是在所有 [PointerUpEvent] 处理完后执行。以确保竞争不会造成卡顿,从而阻止用户与应用程序交互
void sweep(int pointer) {
/// 获取前面 GestureRecognizer#addPointer 时添加的成员 _GestureArena 对象
final _GestureArena? state = _arenas[pointer];
if (state == null) { /// 为空,表示竞技场要么从未存在过,要么就是冲突已经解决
return; // 直接返回
}
assert(!state.isOpen);
if (state.isHeld) { /// 竞技场是可以被延迟的,如果isHeld设置为true,那就延迟清扫,直到管理者调用release
state.hasPendingSweep = true;
assert(_debugLogDiagnostic(pointer, 'Delaying sweep', state));
return; // This arena is being held for a long-lived member.
}
assert(_debugLogDiagnostic(pointer, 'Sweeping', state));
_arenas.remove(pointer);
if (state.members.isNotEmpty) {
// First member wins. -- 第一个竞争者获取胜利
assert(_debugLogDiagnostic(pointer, 'Winner: ${state.members.first}'));
state.members.first.acceptGesture(pointer);
// Give all the other members the bad news. -- 通知后续其它竞争成员拒绝
for (int i = 1; i < state.members.length; i++) {
state.members[i].rejectGesture(pointer);
}
}
}
}
当手指抬起或已经结束移动/缩放后,调用 GestureArenaManager#sweep 函数,其作用是强制解决竞技场上的冲突,迫使竞技场得出一个决胜者。获取前面 GestureRecognizer#addPointer 时添加的成员 _GestureArena 对象,让其保存的竞争者中的第一位直接获取胜利,后续其它竞争成员拒绝。
注意: 所有竞争者中的第一个,其实就是 Widget 树中,层级最深的手势响应者。
手势竞争结束后,获胜者会调用抽象类 GestureArenaMember#acceptGesture 函数响应点击事件,对于点击事件而言,实际响应由 BaseTapGestureRecognizer#acceptGesture 函数来实现。
4. BaseTapGestureRecognizer#acceptGesture
dart
/// tap.dart-- 用于识别点击手势的手势识别器基类
abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer {
bool _sentTapDown = false;
bool _wonArenaForPrimaryPointer = false;
PointerDownEvent? _down;
@protected
void handleTapDown({required PointerDownEvent down}); /// handleTapDown 是抽象函数,具体由子类来实现
@override
void acceptGesture(int pointer) {
/// 标记自己已经取得手势竞争的胜利
super.acceptGesture(pointer);
if (pointer == primaryPointer) {
_checkDown(); /// 首先是进行 down 事件消费
_wonArenaForPrimaryPointer = true; /// 设置 ture 标志为胜利区域
_checkUp(); /// 最后是进行 up 事件消费
}
}
void _checkDown() {
if (_sentTapDown) {
return; /// 如果已经处理过了,就不会再次处理,直接返回
}
/// handleTapDown 是抽象函数,以点击事件为例,由子类 TapGestureRecognizer#handleTapDown 具体实现
handleTapDown(down: _down!);
_sentTapDown = true;
}
}
BaseTapGestureRecognizer 是用于识别点击手势的手势识别器基类,在其 BaseTapGestureRecognizer#acceptGesture 函数中,先标记自己已经取得手势竞争的胜利,随后调用 _checkDown 函数进行 down 事件消费。_checkDown 函数内继续调用抽象函数 handleTapDown 进行下一步处理,对于点击事件来说,其最终由子类 TapGestureRecognizer#handleTapDown 来具体实现。
5. TapGestureRecognizer#handleTapDown
dart
/// tap.dart
class TapGestureRecognizer extends BaseTapGestureRecognizer {
TapGestureRecognizer({super.debugOwner, super.supportedDevices, super.allowedButtonsFilter});
@protected
@override
void handleTapDown({required PointerDownEvent down}) {
final TapDownDetails details = TapDownDetails(
globalPosition: down.position,
localPosition: down.localPosition,
kind: getKindForPointer(down.pointer),
);
switch (down.buttons) {
case kPrimaryButton:
if (onTapDown != null) {
invokeCallback<void>('onTapDown', () => onTapDown!(details));
}
case kSecondaryButton:
if (onSecondaryTapDown != null) {
invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown!(details));
}
case kTertiaryButton:
if (onTertiaryTapDown != null) {
invokeCallback<void>('onTertiaryTapDown', () => onTertiaryTapDown!(details));
}
default:
}
}
}
TapGestureRecognizer#handleTapDown 函数内,获取入参 PointerDownEvent 封装的点击事件信息构建 TapDownDetails 回调信息,最后通过 invokeCallback 调用应用端绑定的回调函数,如:onTapDown 回调。
6. BaseTapGestureRecognizer#_checkUp
dart
/// tap.dart-- 用于识别点击手势的手势识别器基类
abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer {
bool _wonArenaForPrimaryPointer = false;
PointerDownEvent? _down;
PointerUpEvent? _up;
/// handleTapUp 是抽象函数,具体由子类来实现
@protected
void handleTapUp({required PointerDownEvent down, required PointerUpEvent up});
@override
void acceptGesture(int pointer) {
/// 标记自己已经取得手势竞争的胜利
super.acceptGesture(pointer);
if (pointer == primaryPointer) {
_checkDown(); /// 首先是进行 down 事件消费
_wonArenaForPrimaryPointer = true;
_checkUp(); /// 最后是进行 up 事件消费
}
}
void _checkUp() {
if (!_wonArenaForPrimaryPointer || _up == null) {
return; /// PointerUpEvent 为空或者不是手势竞争的胜利者,则直接返回
}
assert(_up!.pointer == _down!.pointer);
/// handleTapUp 是抽象函数,以点击事件为例,由子类 TapGestureRecognizer#handleTapUp 具体实现
handleTapUp(down: _down!, up: _up!);
_reset();
}
}
BaseTapGestureRecognizer#acceptGesture 函数先消费 down 事件,后调用 _checkUp 函数消费 up 事件,_checkUp 函数内继续调用抽象函数 handleTapUp 进行下一步处理,对于点击事件来说,其最终由子类 TapGestureRecognizer#handleTapUp 来具体实现。
5. TapGestureRecognizer#handleTapUp
dart
/// tap.dart
class TapGestureRecognizer extends BaseTapGestureRecognizer {
TapGestureRecognizer({super.debugOwner, super.supportedDevices, super.allowedButtonsFilter});
@protected
@override
void handleTapUp({required PointerDownEvent down, required PointerUpEvent up}) {
final TapUpDetails details = TapUpDetails(
kind: up.kind,
globalPosition: up.position,
localPosition: up.localPosition,
);
switch (down.buttons) {
case kPrimaryButton:
if (onTapUp != null) { /// 如果 onTapUp 回调不为空,则先执行 onTapUp
invokeCallback<void>('onTapUp', () => onTapUp!(details));
}
if (onTap != null) {
invokeCallback<void>('onTap', onTap!);
}
case kSecondaryButton:
if (onSecondaryTapUp != null) {
invokeCallback<void>('onSecondaryTapUp', () => onSecondaryTapUp!(details));
}
if (onSecondaryTap != null) {
invokeCallback<void>('onSecondaryTap', () => onSecondaryTap!());
}
case kTertiaryButton:
if (onTertiaryTapUp != null) {
invokeCallback<void>('onTertiaryTapUp', () => onTertiaryTapUp!(details));
}
default:
}
}
}
TapGestureRecognizer#handleTapUp 函数内,获取入参 PointerUpEvent 封装的点击事件信息构建 TapUpDetails 回调信息,最后通过 invokeCallback 调用应用端绑定的回调函数,如:onTap 回调。
总结
- 触摸事件从应用层通过 Native 层传递到 Dart 层,通过 PlatformDispatcher#_unpackPointerDataPacket 函数把传递来的 ByteData packet 数据整理为 PointerDataPacket 数据,即当前点击(触摸)事件的点击位置在当前屏幕空间的坐标值,并将其交给 GestureBinding 进行处理;
- 命中测试是在 Down 事件触发时进行,在此阶段,命中测试 HitTest 从渲染树的根节点开始,递归将能响应事件的控件添加到HitTestResult#_path 集合中,最后 GestureBinding 将自己添加到集合的尾部,由其对集合中的每一个控件进行事件分发;
- 并不是所有控件都会实现 handleEvent 方法处理事件,主要是 RawGestureDetector 来进行处理。在 Down 事件时,所有的竞争者被添加到 _GestureArena 竞技场中进行竞争,最后由 GestureBinding 关闭竞技场,这时如果区域中只有一个竞争者 RawGestureDetector ,则在 Down 事件阶段这个控件直接获得胜利。如果区域中有多个竞争者,此时如果 _GestureArena#eagerWinner 即渴望获胜者不为空,则交给它来处理,否者在此阶段是不会产生胜利者的。
- 在 Down 事件阶段,如果没有决出胜利者,则当 Up 事件到来时,竞技场 sweep 清洗时选取排在第一位置的 RawGestureDetector 作为胜利者,并调用其 acceptGesture 方法响应触摸事件,即回调 onTapDown 、onTap 等函数。
- 对于 Up 和 Cancel 事件,只有竞争获胜者才能消费 Up 事件,失败者是没有机会的,只能消费 Cancel 事件;
- Move 事件在不同 GestureRecognizer 中会表现不同,具体可查看源码。