持续创作,加速成长!这是我参与「日新计划 6 月更文挑战」的第1天,点击查看活动详情

前面我们介绍了 runAppBaseBinding,知道了 Flutter初始化就是一堆 Binding 的初始化和服务注册,GestureBinding 是第一个初始化的 Binding,见名知意,这个 Binding 是专门用来处理手势的,这一篇就看看 Flutter 是怎么处理手势的。

Flutter 必知必会系列 —— 从 GestureBinding 中看 Flutter 手势处理过程

往期精彩:

Flutter 必知必会系列 —— runApp 做了啥

Flutter 必知必会系列 —— mixin 和 BindingBase 的巧妙配合

Flutter 的手势从哪里来

Flutter 仅仅是一个 UI 框架,是没有从屏幕读取手势功能的,它只能被动的响应。我们以 Android 为例,来找一下手势是怎么传递的。

首先看 Flutter 侧的手势起点。

/// GestureBinding
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    window.onPointerDataPacket = _handlePointerDataPacket;
  }
}

GestureBinding 仅仅是给 onPointerDataPacket 赋值,所以我们看赋值的动作干了啥。

class SingletonFlutterWindow extends FlutterWindow {
    PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket;
    set onPointerDataPacket(PointerDataPacketCallback? callback) {
      platformDispatcher.onPointerDataPacket = callback; //第一处
    }
 }       

我们看第一处的代码,仅仅是给 platformDispatcher 赋值,我们继续追。

platformDispatcher 是 PlatformDispatcher 的实例,并且 PlatformDispatcher 是单例的,所以说一个 Native 的 FlutterView 对应一个 Flutter 的 PlatformDispatcher 实例。(这里大家需要理解一点,Native 的 FlutterView 对应的就是 Flutter 程序,容器是承载 FlutterView 的 Activity 或者 Fragment

见名知意,这个类就是 Native 消息的分发器。我们看赋值代码。

/// PlatformDispatcher
class PlatformDispatcher {
    PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket;
    PointerDataPacketCallback? _onPointerDataPacket;
    Zone _onPointerDataPacketZone = Zone.root;
    set onPointerDataPacket(PointerDataPacketCallback? callback) {
      _onPointerDataPacket = callback;
      _onPointerDataPacketZone = Zone.current;
    }
}  

所以,GestureBinding 中的 window 赋值语句,其实是给 PlatformDispatcher_onPointerDataPacket 赋值,那么调用 _onPointerDataPacket 的地方,就是发起 Flutter 手势的地方。

class PlatformDispatcher {
    // Called from the engine, via hooks.dart
    void _dispatchPointerDataPacket(ByteData packet) {
      if (onPointerDataPacket != null) {
        _invoke1<PointerDataPacket>(
          onPointerDataPacket,
          _onPointerDataPacketZone,
          _unpackPointerDataPacket(packet),
        );
      }
    }
}     

就是 PlatformDispatcher 的 _dispatchPointerDataPacket 方法,而调用 _dispatchPointerDataPacket 的是

Flutter 必知必会系列 —— 从 GestureBinding 中看 Flutter 手势处理过程

我们看到调用的方法是被 @pragma(‘vm:entry-point’) 注解的,也就是从 Native 调用的,而非 Flutter 层。

所以,就是我们前面得到的结论: Flutter 层只是被动的响应来自 Native 的调用,调用的入口是 _dispatchPointerDataPacket

既然是 Native 层,那就是 FlutterView 了,大概率是重写 onTouch 方法。我们看 Native 的处理逻辑,既然承载 Flutter 的是 FlutterView,我们看其手势处理方法。

/// FlutterView
public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseCursorViewDelegate {
    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
      if (!isAttachedToFlutterEngine()) {
        return super.onTouchEvent(event);
      }
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        requestUnbufferedDispatch(event);
      }
      return androidTouchProcessor.onTouchEvent(event);
    }
}

基本就是讲手势透传给了 androidTouchProcessor, androidTouchProcessor 是谁呢?AndroidTouchProcessor 描述如下:

Sends touch information from Android to Flutter in a format that Flutter understands.
把手势的信息从 Android 层传给 Flutter,关键是对信息进行了格式转换。

我们看它的逻辑:


/// AndroidTouchProcessor
public class AndroidTouchProcessor {
    public boolean onTouchEvent(@NonNull MotionEvent event) {
      return onTouchEvent(event, IDENTITY_TRANSFORM);
    }
    public boolean onTouchEvent(@NonNull MotionEvent event, Matrix transformMatrix) {
      int pointerCount = event.getPointerCount();
      // 准备数据包
      ByteBuffer packet =
          ByteBuffer.allocateDirect(pointerCount * POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD);
      packet.order(ByteOrder.LITTLE_ENDIAN);
      int maskedAction = event.getActionMasked();
      int pointerChange = getPointerChangeForAction(event.getActionMasked());
      if (updateForSinglePointer) {
        //添加数据包  DOWN 事件
        addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet);
      } else if (updateForMultiplePointers) {
        //添加数据包 UP 事件
        addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet);
      } else {
        for (int p = 0; p < pointerCount; p++) {
          // 添加数据包  MOVE 事件
          addPointerForIndex(event, p, pointerChange, 0, transformMatrix, packet);
        }
      }
      // 数据包发送给 Flutter
      renderer.dispatchPointerDataPacket(packet, packet.position());
      return true;
    }
}    

onTouchEvent 的处理:

准备数据包,将数据封装成 Flutter 认识的格式,添加到数据包中,renderer 发送给 Flutter。

我们先看封装的格式,在 addPointerForIndex 中:


//代码片段
packet.putLong(motionEventId); // motionEventId
packet.putLong(timeStamp); // time_stamp
packet.putLong(pointerChange); // change
packet.putLong(pointerKind); // kind
packet.putLong(signalKind); // signal_kind
packet.putLong(event.getPointerId(pointerIndex)); // device
packet.putLong(0);
if (signalKind == PointerSignalKind.SCROLL) {
  packet.putDouble(-event.getAxisValue(MotionEvent.AXIS_HSCROLL)); // scroll_delta_x
  packet.putDouble(-event.getAxisValue(MotionEvent.AXIS_VSCROLL)); // scroll_delta_y
} else {
  packet.putDouble(0.0); // scroll_delta_x
  packet.putDouble(0.0); // scroll_delta_x
}

这一段代码是不是和 Flutter 侧的代码很像呀:

Flutter 必知必会系列 —— 从 GestureBinding 中看 Flutter 手势处理过程

我们看 render : FlutterRenderer 是怎么把数据包发送过去的。

/// FlutterRenderer
public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) {
  flutterJNI.dispatchPointerDataPacket(buffer, position);
}
/// FlutterJNI
@UiThread
public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) {
  ensureRunningOnMainThread();
  ensureAttachedToNative();
  nativeDispatchPointerDataPacket(nativeShellHolderId, buffer, position);
}
private native void nativeDispatchPointerDataPacket(
    long nativeShellHolderId, @NonNull ByteBuffer buffer, int position);

调用的就是 JNI 的代码,JNI 调用到了 Engine 底层的代码,尽而调用到了 Flutter 层,如下图中 engine 的代码。

Flutter 必知必会系列 —— 从 GestureBinding 中看 Flutter 手势处理过程

下个系列详细介绍 C++ 层是怎么和 Flutter 交互的,这个系列暂且只需要知道 Flutter 层设置了一个手势处理方法 PlatformDispatcher_dispatchPointerDataPacket ,当识别到手势交互时,Android 的 FlutterView 对原始的手势进行了格式的封装,然后通过 JNI 调用到了上面的 _dispatchPointerDataPacket 即可。

现在知道了手势怎么来的,下面我们来看怎么处理手势的。

Flutter 是怎么处理手势的

上面讲的都是 FlutterNative 交互的东西,对于我们使用者来说,我们的起点就是 GestureBinding_handlePointerDataPacket 方法,只要知道它的逻辑,就知道 Flutter 的手势逻辑了。


// GestureBinding 
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
  _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio)); //第一处
  if (!locked)
    _flushPointerEventQueue();
}
void _flushPointerEventQueue() {
  while (_pendingPointerEvents.isNotEmpty)
    handlePointerEvent(_pendingPointerEvents.removeFirst()); //第二处
}

Native 调用 Flutter 的时候是一个数据包,所以需要将数据包进行转换,转换的结果就是 _pendingPointerEvents : Queue 队列,然后 flush 队列

flush 就是从前往后(先进先出)的边移除边处理每一个事件。

比如:队列中有三个事件:A、B、C。

那么:会依次调用 handlePointerEvent(A)、handlePointerEvent(B)、handlePointerEvent(C)。

那我们继续看 handlePointerEvent 是怎么处理的。

/// GestureBinding
void handlePointerEvent(PointerEvent event) {
  ///... 省略代码
  _handlePointerEventImmediately(event);
}
void _handlePointerEventImmediately(PointerEvent event) {
  HitTestResult? hitTestResult;
  if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) {
    hitTestResult = HitTestResult();
    hitTest(hitTestResult, event.position);
    if (event is PointerDownEvent) {
      _hitTests[event.pointer] = hitTestResult;
    }// 第一处
  } else if (event is PointerUpEvent || event is PointerCancelEvent) {
    hitTestResult = _hitTests.remove(event.pointer); //第二处
  } else if (event.down) {
    hitTestResult = _hitTests[event.pointer];
  }
  if (hitTestResult != null ||
      event is PointerAddedEvent ||
      event is PointerRemovedEvent) {
    dispatchEvent(event, hitTestResult); //第三处
  }
}

handlePointerEvent 就是直接调用了 _handlePointerEventImmediately 方法,在方法内部进行了事件的处理。和我们预想的是不是很像,根据事件类型处理不同的事情。

一般情况下: Down 事件用来记录,Up 事件用来结束,其他的事件做分发。就是对应了第一处、第二处和第三处。

我们先看 PointerDownEvent 事件是怎么记录事件开始的。

Down 事件记录开始

/// GestureBinding
void _handlePointerEventImmediately(PointerEvent event) {
  HitTestResult? hitTestResult;
  if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) {
    hitTestResult = HitTestResult(); //第一处
    hitTest(hitTestResult, event.position); //第二处
    if (event is PointerDownEvent) {
      _hitTests[event.pointer] = hitTestResult;
    }
  }
  /// 代码省略...
  if (hitTestResult != null ||
      event is PointerAddedEvent ||
      event is PointerRemovedEvent) {
    dispatchEvent(event, hitTestResult); //第三处
  }
}

就做了下面件事:

  • ① 声明 HitTestResult ,我们可以认为 HitTestResult 是一个集合,集合中存放的就是可以接受处理这个落点的所有对象。

  • ② 发起点击检测,就是看落点是不是在某个 RenderObject

  • _hitTestsMap 中添加第一处声明的 HitTestResult ,Map 的 Key 是手势的 id,_hitTests 是 GestureBinding 的成员变量,所以每一个事件序列都会以 Map 的形式保存下来。

下面我们详细看。

第一处是声明了 hitTestResult ,并在第二处直接调用了 hitTest

Flutter 必知必会系列 —— 从 GestureBinding 中看 Flutter 手势处理过程

注意看,hitTest 是有被重写的表示的,重写的地方就是 RenderBinding,方法如下:

/// RenderBinding
@override
void hitTest(HitTestResult result, Offset position) {
  renderView.hitTest(result, position: position);
  super.hitTest(result, position);
}

注意看,调用了根 RenderView 的点击检测,然后再调用上面 GestureBinding 的点击检测。

RenderView 的点击检测做了啥呢?

/// RenderView
bool hitTest(HitTestResult result, { required Offset position }) {
  if (child != null)
    child!.hitTest(BoxHitTestResult.wrap(result), position: position);
  result.add(HitTestEntry(this)); 
  return true;
}
/// RenderObject
bool hitTest(BoxHitTestResult result, { required Offset position }) {
  if (_size!.contains(position)) {
    if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
      result.add(BoxHitTestEntry(this, position));
      return true;
    }
  }
  return false;
}

就做了两件事:

  • ① 发起子节点的落点检测,这个过程是一个递归的过程,会将所有宽高包含的子孙节点都添加到集合中

  • ② 将 根View 添加到集合中

这就是 根View 的落点检测过程,现在回过头看:

/// RenderBinding
@override
void hitTest(HitTestResult result, Offset position) {
  renderView.hitTest(result, position: position);
  super.hitTest(result, position);
}

renderView.hitTest 就是添加了所有的符合条件的 RenderObjectsuper 是什么呢?superGestureBinding 的 hitTest,如果不熟悉可以看前面的 Flutter 必知必会系列 —— mixin 和 BindingBase 的巧妙配合,如下:

Flutter 必知必会系列 —— 从 GestureBinding 中看 Flutter 手势处理过程

把 GestureBinding 本身添加到了集合中。我们举个例子:

Flutter 必知必会系列 —— 从 GestureBinding 中看 Flutter 手势处理过程

落点落在了 B 上,B 是 A 的子节点,那么形成的 hitTestResult 中会维护上图中黑色的列表,最巧妙的地方是 HitTestResult 维护的列表中最后一个元素是 Binding。这样就形成了:Binding 是手势的起点和终点,至于手势上的有哪些节点,它并不关心,它只负责处理框架层的东西

那么框架层的东西是啥呢?

就是 down 事件之后的 dispatchEvent

dispatchEvent 分发处理事件

/// GestureBinding
void _handlePointerEventImmediately(PointerEvent event) {
  HitTestResult? hitTestResult;
  if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) {
    hitTestResult = HitTestResult();
    hitTest(hitTestResult, event.position);
    if (event is PointerDownEvent) {
      _hitTests[event.pointer] = hitTestResult;
    }
  } else if (event is PointerUpEvent || event is PointerCancelEvent) {
    hitTestResult = _hitTests.remove(event.pointer);
  } else if (event.down) {
    hitTestResult = _hitTests[event.pointer];
  }
  if (hitTestResult != null ||
      event is PointerAddedEvent ||
      event is PointerRemovedEvent) {
    dispatchEvent(event, hitTestResult); //第一处
  }
}

我们再看 _handlePointerEventImmediately 方法,在判断完具体的事件类型之后,就走到了第一处代码处

见名知意,这个就是分发。分发就是将事件分给应该处理的节点,比如上图中的 A 和 B 等等。

/// GestureBinding
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
  ///... 代码省略
  for (final HitTestEntry entry in hitTestResult.path) {
    try {
      entry.target.handleEvent(event.transformed(entry.transform), entry);
    } catch (exception, stack) {
    }
  }
}

注意一点: 在循环遍历中,最后一个元素是 Binding,其他的元素都是RenderObject,我们暂且不看,只看框架层的东西。

/// GestureBinding
@override 
void handleEvent(PointerEvent event, HitTestEntry entry) {
  pointerRouter.route(event); //第一处
  if (event is PointerDownEvent) { //第二处
    gestureArena.close(event.pointer);
  } else if (event is PointerUpEvent) {
    gestureArena.sweep(event.pointer);
  } else if (event is PointerSignalEvent) {
    pointerSignalResolver.resolve(event);
  }
}

第一处就是发起手势的处理,pointerRouter 是路由器的意思,route方法 就是路由到具体的处理。

第二处就是针对不同的事件框架应该干啥事,这里涉及到了手势竞技场的概念。

我们具体来看,先看路由的处理。

///PointerRouter
void route(PointerEvent event) {
  final Map<PointerRoute, Matrix4?>? routes = _routeMap[event.pointer];
  final Map<PointerRoute, Matrix4?> copiedGlobalRoutes = Map<PointerRoute, Matrix4?>.from(_globalRoutes);
  if (routes != null) {
    _dispatchEventToRoutes(
      event,
      routes,
      Map<PointerRoute, Matrix4?>.from(routes),
    );
  }
  _dispatchEventToRoutes(event, _globalRoutes, copiedGlobalRoutes);
}
void _dispatchEventToRoutes(
  PointerEvent event,
  Map<PointerRoute, Matrix4?> referenceRoutes,
  Map<PointerRoute, Matrix4?> copiedRoutes,
) {
  copiedRoutes.forEach((PointerRoute route, Matrix4? transform) {
    if (referenceRoutes.containsKey(route)) {
      _dispatch(event, route, transform);
    }
  });
}
@pragma('vm:notify-debugger-on-exception')
void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) {
  try {
    event = event.transformed(transform);
    route(event); //第一处
  } catch (exception, stack) {
  }
}

_routeMap 是一个 Map ,Map 的 key 就是事件序列的 id ,value 是 PointerRoute 是事件的处理回调。

_dispatchEventToRoutes 就是分发,具体走到了 _dispatch,然后走到了第一处的 route(event)

这里注意一点: PointerRoute 是一个函数签名,就是 _routeMap 中的 value,也就是说 谁注册了,它就路由到谁

那么谁注册了呢?

就是调用 addRoute 的地方,具体就是手势收到落点的地方。

/// GestureRecognizer
void startTrackingPointer(int pointer, [Matrix4? transform]) {
  GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform); //第一处
  _trackedPointers.add(pointer);
  _entries[pointer] = _addPointerToArena(pointer);
}

在手势需要追踪的时候,就调用到了 第一处的代码,第一处就是具体的手势注册代码,注册的 PointerRoute 就是手势的 handleEvent

所以流程就是: 具体的手势注册自己的事件处理方法,然后框架层负责调用

我们再看手势竞技场干了啥。

/// GestureRecognizer
void handleEvent(PointerEvent event, HitTestEntry entry) {
  pointerRouter.route(event);
  if (event is PointerDownEvent) {
    gestureArena.close(event.pointer);
  } else if (event is PointerUpEvent) {
    gestureArena.sweep(event.pointer);
  } else if (event is PointerSignalEvent) {
    pointerSignalResolver.resolve(event);
  }
}

如果是 PointerDownEvent 事件,那么 gestureArena 就关闭掉,关闭的意思是:

  • 如果竞技场中只有一个元素,那么该元素就获胜,获得获得手势处理权,就开始处理手势。

  • 如果竞技场中没有元素,那么没有元素获胜。

  • 如果竞技场中有特别渴望胜利的,那么特别渴望胜利的就获得手势处理权,比如 EagerGestureRecognizer,这个机会很少,因为会打破默认的处理。

如果是 PointerUpEvent 事件,那么就需要竞争了 sweep 。竞争的结果就是第一个获得手势处理权,其他的手势拒绝手势。

上面就是框架层的手势处理,下面我们看看具体的手势组件。

手势组件源码解读

手势处理组件我们常用的就是 ListenerGestureDetector 等等。GestureDetector 包裹的就是 Listener,下面我们看看它俩的源码。

Listener 源码

class Listener extends SingleChildRenderObjectWidget {
  const Listener({
    Key? key,
    this.onPointerDown,
    this.onPointerMove,
    this.onPointerUp,
    this.onPointerHover,
    this.onPointerCancel,
    this.onPointerSignal,
    this.behavior = HitTestBehavior.deferToChild,
    Widget? child,
  }) : assert(behavior != null),
       super(key: key, child: child);
  @override
  RenderPointerListener createRenderObject(BuildContext context) {
    return RenderPointerListener(
      onPointerDown: onPointerDown,
      onPointerMove: onPointerMove,
      onPointerUp: onPointerUp,
      onPointerHover: onPointerHover,
      onPointerCancel: onPointerCancel,
      onPointerSignal: onPointerSignal,
      behavior: behavior,
    );
  }
}

ListenerSingleChildRenderObjectWidget 类型的组件, 构造方法中的 onPointerDown 中的就是 Down 事件的回调。具体的逻辑在 RenderPointerListener 中。

/// RenderPointerListener
class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
  @override
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    if (event is PointerDownEvent)
      return onPointerDown?.call(event);
    if (event is PointerMoveEvent)
      return onPointerMove?.call(event);
    if (event is PointerUpEvent)
      return onPointerUp?.call(event);
    if (event is PointerHoverEvent)
      return onPointerHover?.call(event);
    if (event is PointerCancelEvent)
      return onPointerCancel?.call(event);
    if (event is PointerSignalEvent)
      return onPointerSignal?.call(event);
  }
 }

我们看到了 handleEvent 方法,针对每一种类型处理了不同的回调。

还记得 handleEvent 是谁调用的吗? handleEvent 是从 RenderObject 继承的方法。

// GestureBinding
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
  for (final HitTestEntry entry in hitTestResult.path) {
    try {
      entry.target.handleEvent(event.transformed(entry.transform), entry);
    } catch (exception, stack) {
    }
  }
}

就是这里的 handleEvent 方法,hitTestResultpath 就是我们的RenderObject,也就是 RenderPointerListener,所以 entry.target.handleEvent 会走到 handleEvent 中。

注意一点: 如果我们代码只是单纯的使用了 Listener ,并不会走到框架的手势处理,只会走到 entry.target.handleEvent 就结束了

下面我们看手势的组件处理。

GestureDetector 源码

GestureDetectorStatelessWidget,为每一种手势都封装了一个回调,比如点击的就是 onTapDown,其逻辑都在 build 方法中。

/// GestureDetector 
@override
Widget build(BuildContext context) {
  final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
  if (onTapDown != null ||
      onTapUp != null ||
      onTap != null ||
      onTapCancel != null ||
      onSecondaryTap != null ||
      onSecondaryTapDown != null ||
      onSecondaryTapUp != null ||
      onSecondaryTapCancel != null||
      onTertiaryTapDown != null ||
      onTertiaryTapUp != null ||
      onTertiaryTapCancel != null
  ) {
    gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
      () => TapGestureRecognizer(debugOwner: this),
      (TapGestureRecognizer instance) {
        instance
          ..onTapDown = onTapDown
          ..onTapUp = onTapUp
          ..onTap = onTap
          ..onTapCancel = onTapCancel
          ..onSecondaryTap = onSecondaryTap
          ..onSecondaryTapDown = onSecondaryTapDown
          ..onSecondaryTapUp = onSecondaryTapUp
          ..onSecondaryTapCancel = onSecondaryTapCancel
          ..onTertiaryTapDown = onTertiaryTapDown
          ..onTertiaryTapUp = onTertiaryTapUp
          ..onTertiaryTapCancel = onTertiaryTapCancel;
      },
    );
  }
 /// 代码省略
  return RawGestureDetector(
    gestures: gestures,
    behavior: behavior,
    excludeFromSemantics: excludeFromSemantics,
    child: child,
  );
}

逻辑比较简单:根据我们设置的回调生成手势,比如 onTapDown 就是 TapGestureRecognizer 手势,然后将手势封装到了 gestures 中,并在最后返回了 RawGestureDetector 组件。

RawGestureDetector 是 StatefulWidget 类型的组件,其逻辑在 RawGestureDetectorState 的 initState 和 build 方法中。

/// RawGestureDetectorState
void initState() {
  super.initState();
  _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
  _syncAll(widget.gestures);
}
void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
  final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
  _recognizers = <Type, GestureRecognizer>{};
  for (final Type type in gestures.keys) {
    _recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
    gestures[type]!.initializer(_recognizers![type]!); //第一处
  }
}

初始化的过程就是 gestures 中手势初始化的过程,这个初始化就是手势中回调的赋值。比如 TapGestureRecognizer 的 onTapDown 赋值为我们开发者设置的自定义回调,重点是第一处的代码。这里用到了工厂模式大家可以细细品味一下

build 的逻辑如下:

/// RawGestureDetectorState
@override
Widget build(BuildContext context) {
  Widget result = Listener(
    onPointerDown: _handlePointerDown,
    behavior: widget.behavior ?? _defaultBehavior,
    child: widget.child,
  );
  return result;
}

我们看,就是返回了 Listener 组件,然后给 ListenerDown 事件赋值为 _handlePointerDown 回调,我们知道 Listener 是不处理手势的,只负责接受 Down 等事件的回调,所以 Down 事件的时候,会调用到_handlePointerDown 方法中。

/// RawGestureDetectorState
void _handlePointerDown(PointerDownEvent event) {
  for (final GestureRecognizer recognizer in _recognizers!.values)
    recognizer.addPointer(event);
}

我们看代码比较简单,就是将手势注册到了路由中。具体如下:

/// GestureRecognizer
void addPointer(PointerDownEvent event) {
  _pointerToKind[event.pointer] = event.kind;
  if (isPointerAllowed(event)) {
    addAllowedPointer(event);
  } else {
    handleNonAllowedPointer(event);
  }
}
@override
@protected
void addAllowedPointer(PointerDownEvent event) {
  startTrackingPointer(event.pointer, event.transform);
}
@protected
void startTrackingPointer(int pointer, [Matrix4? transform]) {
  GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);//第一处
  _trackedPointers.add(pointer);
  assert(!_entries.containsValue(pointer));
  _entries[pointer] = _addPointerToArena(pointer);
}

我们看最终调用到了手势的 startTrackingPointer 中,我们看第一处的代码,第一处就是我们前面介绍的注册 handleEvent 的地方。

那么 handleEvent 是啥呢?

/// PrimaryPointerGestureRecognizer
@override
void handleEvent(PointerEvent event) {
  if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
     /// 代码省略
    if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
      resolve(GestureDisposition.rejected);
      stopTrackingPointer(primaryPointer!);
    } else {
      handlePrimaryPointer(event); //第一处
    }
  }
  stopTrackingIfPointerNoLongerDown(event);
}

我们看第一处代码,第一处代码就是手势接收到 Down 事件的处理。

/// PrimaryPointerGestureRecognizer
@override
void handlePrimaryPointer(PointerEvent event) {
  if (event is PointerUpEvent) {
    _up = event;
    _checkUp();
  } else if (event is PointerCancelEvent) {
    resolve(GestureDisposition.rejected);
    if (_sentTapDown) {
      _checkCancel(event, '');
    }
    _reset();
  } else if (event.buttons != _down!.buttons) {
    resolve(GestureDisposition.rejected);
    stopTrackingPointer(primaryPointer!);
  }
}

上面方法中,我们发现没有关于 Down 事件的处理,所以 HitTestResult 的 path 中走到了 Binding 的处理。

/// GestureBinding
@override 
void handleEvent(PointerEvent event, HitTestEntry entry) {
  pointerRouter.route(event);
  if (event is PointerDownEvent) {
    gestureArena.close(event.pointer);
  } else if (event is PointerUpEvent) {
    gestureArena.sweep(event.pointer);
  } else if (event is PointerSignalEvent) {
    pointerSignalResolver.resolve(event);
  }
}

也就是 gestureArena.close(event.pointer) 的代码,根据我们上面的介绍,如果我们只设置一个 onTapDown 的话,直接关闭竞技场:

void _tryToResolveArena(int pointer, _GestureArena state) {
  if (state.members.length == 1) {
    scheduleMicrotask(() => _resolveByDefault(pointer, state)); //第一处
  } else if (state.members.isEmpty) {
    _arenas.remove(pointer);
  } else if (state.eagerWinner != null) {
    _resolveInFavorOf(pointer, state, state.eagerWinner!);
  }
}
void _resolveByDefault(int pointer, _GestureArena state) {
  if (!_arenas.containsKey(pointer))
    return; // Already resolved earlier.
  final List<GestureArenaMember> members = state.members;
  _arenas.remove(pointer);
  state.members.first.acceptGesture(pointer); //第二处
}

也就是第一处代码的地方。点击手势直接获胜。走到第二处代码,也就是手势的 acceptGesture 方法进行调用。

@override
void acceptGesture(int pointer) {
  super.acceptGesture(pointer);
  if (pointer == primaryPointer) {
    _checkDown();
    _wonArenaForPrimaryPointer = true;
    _checkUp();
  }
}
void _checkDown() {
  if (_sentTapDown) {
    return;
  }
  handleTapDown(down: _down!);
  _sentTapDown = true;
}
@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)); //第一处
      break;
    case kSecondaryButton:
      if (onSecondaryTapDown != null)
        invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown!(details));
      break;
    case kTertiaryButton:
      if (onTertiaryTapDown != null)
        invokeCallback<void>('onTertiaryTapDown', () => onTertiaryTapDown!(details));
      break;
    default:
  }
}

最终会调用到第一处代码,也就是调用到了我们的自定义的 onTapDown 回调中。

上面就是手势组件的处理过程。

总结

上面我们就从宏观上介绍了手势的处理过程,从 Native 中接受到事件的数据序列,GestureBinding 接受事件序列,然后进行格式转换,从框架层进行处理。如果没有手势参与的话,就直接调用了具体的 handle 事件。如果有手势参与的话,那么就通过手势竞技场来调用注册好的手势回调。

更加细节的手势处理,可以在此基础上进行深究,比如多个手势怎么具体的进行获胜判断的等等。