探索 Flutter 事件机制

Flutter源码探索

第一章 探索 Flutter 事件机制


文章目录


前言

App 开发中,与用户交互的逻辑几乎是随处可见的,如:用户点击某个按钮、双击屏幕及滑动页面等。在 Flutter 中,事件机制作为其核心之一,用于响应用户输入和其他形式的交互逻辑。

Flutter 的事件机制是基于监听器模式实现的,主要通过以下几种方式来处理事件:

  1. 监听器 (Listener): Flutter 提供了 GestureDetectorRawGestureDetector 等小部件,用于监听各种手势事件,如点击、滑动、缩放等。
  2. 焦点节点 (FocusNode): 用于管理文本字段(如文本输入框:TextField)的焦点。通过焦点节点,你可以监听文本字段的焦点变化事件。
  3. 键盘事件 (Keyboard Events): 通过 RawKeyboardListener 监听键盘事件,如:按键按下和释放。
  4. 触摸事件 (Touch Events) 和指针事件 (Pointer Events): Flutter 提供了更底层的触摸和指针事件处理机制,例如 ListenerPointerListener,用于更精细的控制触摸事件。

我们知道,用户与 App 的交互行为是在原生设备上进行。以触摸事件为例,当用户在设备上触摸屏幕时,操作系统(如:AndroidiOS)首先接收到这个触摸事件。这些事件通常以原始形式(如:触摸点的位置、压力等)被捕获。

原生层捕获到事件后,只是将所有事件打包下发,如在 Android 中,触摸手势信息被打包成 ByteBuffer ,然后从 Native 层传递到 Flutter 中,最后在 Dart 层的 _dispatchPointerDataPacket 函数中,通过 _unpackPointerDataPacket 函数解析成可用的 PointerDataPacket 对象使用。其大致流程如下图所示:

本文将跟随源码来探索 Flutter 事件传递、命中测试、事件分发、事件监听、手势竞争及响应。


一、事件传递

触摸事件在 Native 层经过转换后,通过 Tonic::DartInvoke() 拉起 Flutter 端的 _dispatchPointerDataPacket 函数,该函数就是 hooks.dart 文件的 _dispatchPointerDataPacket 函数。

Tonic::DartInvokeTonic 库中的一个函数,用于在原生层调用 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);
    }
  }

函数的执行流程如下:

  1. 调用 HitTestResult() 函数,先创建一个空的命中测试结果 hitTestResult ,其内部拥有一个 List<HitTestEntry> 类型的 _path 集合,用来存放执行命中测试后的结果集。
  2. 调用 hitTestInView() 函数,执行命中测试相关的业务逻辑,以此确定哪个 HitTestTarget 对象位于指定 View 中的给定位置。
  3. 如果是 down 事件,则将命中测试的结果添加到集合 _hitTests 中。如果是抬起、取消等事件,则从 _hitTests 集合中移除,并返回当前的 hitTestResult 。如果是 move 事件,则通过 event.pointer 获取到之前的 hitTestResult 对象,即集合 _hitTests 中保存的之前 PointerDownEvent 的时候保存下来的 hitTestResult 对象。
  4. 如果 hitTestResult 不为空,或事件是 PointerAddedEventPointerRemovedEvent ,则执行 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

流程追踪到这里没有看到想要的,那就看其子类里面的实现逻辑。通过关键字 mixinon 的混入,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 函数。执行完 RenderViewhitTest 函数后再继续调用 super.hitTestInView(result, position, viewId); 也就是执行 GestureBindinghitTestInView 函数将自身添加到 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 ,用来填充整个输出页面。因此 RenderViewhitTest 函数最终调用的是子节点 RenderBoxhitTest 函数。

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;
}

RenderBoxhitTest 函数的整体逻辑如下:

  1. 先判断事件的触发位置是否位于组件 position 范围内,如果不是则不会通过命中测试,此时 hitTest 返回 false,如果是则继续下一步。
  2. 先调用 hitTestChildren 判断是否有子节点通过命中测试,如果是则将当前节点封装成 BoxHitTestEntry 加入到 HitTestResult 列表,此时 hitTest 返回 true。即只要有子节点通过了命中测试,那么它的父节点(当前节点)也会通过命中测试。
  3. 如果没有子节点通过命中测试,则会取 hitTestSelf 方法的返回值,如果返回值为 true,则当前节点通过命中测试,反之则否。

注意: 如果父子节点都监听了同一个事件,则子节点会比父节点先响应事件。这是因为命中测试过程是按照深度优先规则遍历的,所以子节点会比父节点先加入 HitTestResult 列表,又因为在事件分发时是从前到后遍历 HitTestResult 列表的,所以子节点比父节点会更先被调用 handleEvent

BoxHitTestResultHitTestResult 的子类,BoxHitTestResult 没有重写 HitTestResult#add 函数,所以这里调用的是 HitTestResultadd 函数,将触点信息封装成 BoxHitTestEntry ,然后添加到 _path 集合中。
BoxHitTestEntryHitTestEntry 的子类,相比父类增加了 localPosition 属性,表示命中测试在 target 局部坐标中的位置。

hitTestChildrenhitTestSelf 是两个抽象方法,具体由子类实现。而子类分两种:一种是单子节点的,一种是多子节点的,分别挑一个看下代码的具体实现。

5.1 RenderShiftedBox#hitTestChildren

单子节点 的组件,以 Padding 为例,Padding 对应的 RenderObjectRenderPadding ,其父类是RenderShiftedBoxRenderShiftedBox 继承 RenderBox 并实现了 hitTestChildren 函数,那就看下 RenderShiftedBoxhitTestChildren 函数实现:

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 函数,首先判断该节点是否还有子节点,如有则调用执行 BoxHitTestResultaddWithPaintOffset 函数,其内部会根据是否有偏移量 Offset ,然后通过计算做个偏移,最后调用子节点 RenderBoxhitTest 函数执行子节点的命中测试,并继续上面的步骤,单子节点比较容易看懂,只要还有子节点并且触点信息包含在 Widget 范围之内就一层一层的迭代下去。

5.2 RenderShiftedBox#hitTestChildren

多子节点 的容器类组件,如:RowColumnRowColumn 的父类都是 FlexFlex 对应的 RenderObjectRenderFlexRenderFlex 继承 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);
  }
}

RenderFlexhitTestChildren 函数实现也很简单,调用 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;
  }
}

RenderBoxContainerDefaultsMixindefaultHitTestChildren 函数内,倒序获取子节点集合中的子节点,并执行每个子节点的 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) {
        .../// 省略代码
      }
    }
  }

如果命中测试结果 hitTestResultnull ,则通过 pointerRouter#route 将事件分发到全局处理。否者遍历 hitTestResult.path 集合,执行每一个 HitTestEntryHitTestTarget 对象的 handleEvent 函数处理事件。

HitTestEntryHitTestTarget 对象指向的是谁呢?先来看一下 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';
}

由前面的代码分析可知,每次命中测试的时候,符合条件的节点会封装一些信息到 HitTestEntryBoxHitTestEntry 中,然后添加到 hitTestResult 中保存。观察构建 HitTestEntryBoxHitTestEntry 时传入的参数都有 this 自身,即 HitTestEntry 实例对象的 target 保存的是实现 HitTestTarget 接口的当前控件的 RenderObjectRenderObject 默认都实现了 HitTestTarget 接口,因此 HitTestEntryHitTestTarget 大部分时候都是 RenderObject)。

注意 :并不是所有控件的 RenderObject 类都会实现 handleEvent 函数来处理事件。通过跟踪代码并分析,可知 entry.target.handleEvent 最后是由 RenderPointerListener 实现的 handleEvent 函数来处理事件的。RenderPointerListenerListener (可以监听原始 PointEventWidget )控件对应的 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,
    );
  }
}

GestureDetectorbuild 函数,将手势识别器工厂对象保存到 gestures 中,注意这里的 Key 的类型为具体的手势识别器,Value 是生产对应手势识别器的 GestureRecognizerFactoryWithHandlers 类对象,其继承自 GestureRecognizerFactory 抽象类,是具体的手势识别器工厂类,重写了 GestureRecognizerFactory 类的 constructorinitializer 函数,但是没有写具体的逻辑,而是把逻辑处理交给了 _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 集合内的所有手势识别器做同步处理。

  1. 遍历传入的 gestures 集合,获取并调用 gestures 保存的手势识别器工厂对象的 constructor 函数返回手势识别器填充到 _recognizers 集合中。如果已存在对应类型的 GestureRecognizer,则直接复用而不是创建新的。
  2. 调用手势识别器工厂对象的 initializer 函数绑定监听回调。
  3. RawGestureDetectorbuild 函数返回的是一个 Listener 组件,RenderPointerListenerListener (可以监听原始 PointEventWidget )对应的 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 事件触发 ListeneronPointerDown 回调函数,该事件最终会被交给 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 函数,其内部做了校验和赋值后,继续调用了其父类 PrimaryPointerGestureRecognizeraddAllowedPointer 函数,其内部继续调用父类 OneSequenceGestureRecognizeraddAllowedPointer 函数,之后判断并更新手势识别器的状态信息等。

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 实例是添加到 OneSequenceGestureRecognizerMap<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 事件和自己绑定,并添加到 GestureBindingPointerRouter 事件路由和 _GestureArena 手势竞技场中,后续事件该组件的 GestureRecognizer 才能响应和参与竞争。


五、手势竞争及响应

Flutter 在设计手势竞争的时候,定义了一个很有趣的概念:通过一个竞技场,各个控件参与竞争,直接胜利的或者活到最后的第一位,就获胜得到了胜利。 那么为了分析接下来的手势竞争,我们需要先看几个概念:

  • GestureRecognizer :手势识别器基类,基本上 RenderPointerListener 中需要处理的手势事件,都会分发到它对应的 GestureRecognizer ,并经过它处理和竞争后再分发出去,常见有:OneSequenceGestureRecognizerMultiTapGestureRecognizerVerticalDragGestureRecognizerTapGestureRecognizer 等。
  • GestureArenaManager:手势竞技管理,其管理了整个竞争的过程,原则上竞争胜出的条件是:第一个竞争获胜的成员或最后一个不被拒绝的成员。
  • GestureArenaEntry:提供手势事件竞争信息的实体,内封装参与事件竞争的成员。
  • GestureArenaMember :参与手势竞争的成员抽象对象,内部有 acceptGesturerejectGesture 方法,代表手势竞争的成员,默认 GestureRecognizer 都实现了它,所有竞争的成员可以理解为就是 GestureRecognizer 之间的竞争。
  • _GestureArenaGestureArenaManager 内的竞技场,内部持参与竞技的 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);
    }
  }
}

对于 PointerDownEventPointerPanZoomStartEvent 事件,调用 GestureArenaManager#close 方法关闭竞技场,因为此时参加手势竞争的控件 (竞争参与者 ) 已经确定了;对于 PointerUpEventPointerPanZoomEndEvent 事件,则调用 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 ),那么在 PointerDownEventPointerPanZoomStartEvent 时,如果 _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 回调。


总结

  1. 触摸事件从应用层通过 Native 层传递到 Dart 层,通过 PlatformDispatcher#_unpackPointerDataPacket 函数把传递来的 ByteData packet 数据整理为 PointerDataPacket 数据,即当前点击(触摸)事件的点击位置在当前屏幕空间的坐标值,并将其交给 GestureBinding 进行处理;
  2. 命中测试是在 Down 事件触发时进行,在此阶段,命中测试 HitTest 从渲染树的根节点开始,递归将能响应事件的控件添加到HitTestResult#_path 集合中,最后 GestureBinding 将自己添加到集合的尾部,由其对集合中的每一个控件进行事件分发;
  3. 并不是所有控件都会实现 handleEvent 方法处理事件,主要是 RawGestureDetector 来进行处理。在 Down 事件时,所有的竞争者被添加到 _GestureArena 竞技场中进行竞争,最后由 GestureBinding 关闭竞技场,这时如果区域中只有一个竞争者 RawGestureDetector ,则在 Down 事件阶段这个控件直接获得胜利。如果区域中有多个竞争者,此时如果 _GestureArena#eagerWinner 即渴望获胜者不为空,则交给它来处理,否者在此阶段是不会产生胜利者的。
  4. Down 事件阶段,如果没有决出胜利者,则当 Up 事件到来时,竞技场 sweep 清洗时选取排在第一位置的 RawGestureDetector 作为胜利者,并调用其 acceptGesture 方法响应触摸事件,即回调 onTapDownonTap 等函数。
  5. 对于 UpCancel 事件,只有竞争获胜者才能消费 Up 事件,失败者是没有机会的,只能消费 Cancel 事件;
  6. Move 事件在不同 GestureRecognizer 中会表现不同,具体可查看源码。

参考

  1. 深入进阶-从一次点击探寻Flutter事件分发原理
相关推荐
程序员老刘2 小时前
Flutter凉不了:它是Google年入3000亿美元的胶水
flutter·google·客户端
CrazyQ13 小时前
flutter_easy_refresh在3.38.3配合NestedScrollView的注意要点。
android·flutter·dart
爱吃大芒果3 小时前
从零开始学 Flutter:状态管理入门之 setState 与 Provider
开发语言·javascript·flutter
庄雨山4 小时前
Flutter+开源鸿蒙实战:cached_network_image 图片加载体验优化全指南
flutter·openharmonyos
克喵的水银蛇6 小时前
Flutter 通用标签组件:TagWidget 一键实现多风格标签展示
flutter
克喵的水银蛇6 小时前
Flutter 通用空状态组件:EmptyWidget 一键实现多场景空状态展示
flutter
庄雨山6 小时前
Flutter+开源鸿蒙实战:列表下拉刷新与上滑加载更多完整实现
flutter·openharmonyos
松☆7 小时前
OpenHarmony + Flutter 车机系统开发实战:构建高性能、高安全的智能座舱应用
安全·flutter
kirk_wang7 小时前
在鸿蒙端适配 Flutter `flutter_native_splash` 库:原理、实现与性能优化
flutter·移动开发·跨平台·arkts·鸿蒙