Flutter手势系统详解

原文链接: https://juejin.im/post/5b6ad3afe51d45191a0da92a

Flutter手势系统详解

我们知道,flutter 框架自动生成的入口方法为:

void main() => runApp(new MyApp());

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}
复制代码

而这个 WidgetsFlutterBinding 其实是创建并返回了一个 WidgetsBinding 对象的实例。

abstract class WidgetsBinding extends BindingBase with SchedulerBinding, GestureBinding, RendererBinding {
  // ...
}
复制代码

通过 flutter 的 mixin 机制,BindingBase、SchedulerBinding、GestureBinding、RendererBinding 的 initInstances() 方法会被依次执行并返回。而其中与手势系统相关的有 GestureBinding 和 RendererBinding。

首先关注 GestureBinding#initInstances()

@override
void initInstances() {
  super.initInstances();
  _instance = this;
  ui.window.onPointerDataPacket = _handlePointerDataPacket;
}
复制代码

实现非常简洁。其中,我们只需要关注一句话就行了:

  ui.window.onPointerDataPacket = _handlePointerDataPacket;
复制代码

这个语句将 ui.window 获取到 PointerDataPacket 时候的处理方法指向了 GestureBinding#_handlePointerDataPacket

 void _handlePointerDataPacket(ui.PointerDataPacket packet) {
    // We convert pointer data to logical pixels so that e.g. the touch slop can be
    // defined in a device-independent manner.
    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, ui.window.devicePixelRatio));
    if (!locked)
      _flushPointerEventQueue();
  }
复制代码

这里的逻辑依然很直观:在获取到 PointerDataPacket 的数据包之后,将是数据包中的 data 取出,加入到 _pendingPointerEvents中。然后,尝试通过调用 _flushPointerEventQueue();依次处理每一个 pointer event。这里使用了 queue 队列的数据结构,原因很简单:先来的 pointer 事件需要先得到处理。

  void _flushPointerEventQueue() {
    assert(!locked);
    while (_pendingPointerEvents.isNotEmpty)
      _handlePointerEvent(_pendingPointerEvents.removeFirst());
  }	
复制代码

通过调用 _handlePointerEvent(_pendingPointerEvents.removeFirst()); 来实现对每一个 pointer event 的处理。而这个方法也是手势处理系统的核心方法。


  void _handlePointerEvent(PointerEvent event) {
    assert(!locked);
    HitTestResult result;
    if (event is PointerDownEvent) {
      assert(!_hitTests.containsKey(event.pointer));
      result = new HitTestResult();
      hitTest(result, event.position);
      _hitTests[event.pointer] = result;
      assert(() {
        if (debugPrintHitTestResults)
          debugPrint('$event: $result');
        return true;
      }());
    } else if (event is PointerUpEvent || event is PointerCancelEvent) {
      result = _hitTests.remove(event.pointer);
    } else if (event.down) {
      result = _hitTests[event.pointer];
    } else {
      return; // We currently ignore add, remove, and hover move events.
    }
    if (result != null)
      dispatchEvent(event, result);
  }
复制代码

方法中会对 ponter event 的种类进行区分。如果当前 event 是 PointerDownEvent的话,会执行一组手势的初始化逻辑。具体一些,就是先

HitTestResult result;
result = new HitTestResult();
复制代码

然后通过 histTest()来检测这个 pointer event 涉及到哪些 view。具体判断逻辑我们不看代码也可以猜到:

就是根据 pointer event 的点击事件的 position,从 rootView 开始判断这个 position 命中了哪些 view,然后将这些 view 加入到我们刚刚构造的 HitTestResultpath 中。这个 path 的含义的就是手势分发的时候,pointer event 依次经过的路径。

初始化工作完成后,就是具体的分发方法。入口在 dispatchEvent(event, result)

  @override // from HitTestDispatcher
  void dispatchEvent(PointerEvent event, HitTestResult result) {
    for (HitTestEntry entry in result.path) {
      try {
        entry.target.handleEvent(event, entry);
      } catch (exception, stack) {
        // ...
      }
    }
  }
复制代码

去掉 assert 和 catch 中的无关信息,我们可以清楚的看到,分发过程很简单,就是调用了 HitTestTargethandleEvent 方法。其中,根据 path 中 item 的 add 的顺序,childView#handleEventparentView#handleEventGestureBinding#handleEvent依次得到调用。因为 GestureBinding 是最后一个加入到 path 中的,所以 GestureBinding#handleEvent 也是最后得到调用的。在 GestureBinding#handleEvent中的逻辑如下:

  @override // from HitTestTarget
  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);
    }
  }
复制代码

先是 pointerRouter.route(event),我们在自定义手势识别类,比如 CustomRecognizer 的时候,会对其中的 handleEvent(PointerEvent event) 进行 override,并在里面根据我们的自定义手势逻辑执行相应的动作。手势处理完成后,就是手势事件的收尾工作:

  • 如果是 PointerDownEvent,则关闭 gestureArena 手势竞技场
  • 如果是 PointerUpEvent,则清理 gestureArena 手势竞技场

你可能感兴趣的:(Flutter手势系统详解)