温故知新-GestureBinding

42 阅读3分钟

GestureBinding(手势)

  1. 接收来自设备的原始事件;
  2. 将原始事件转化为Flutter事件(PointerDataPacket => PointEvent)
  3. 分发事件到相应的Widget
  4. GestureArenaManager处理多个手势之间的竞争;
  5. 管理手势的生命周期
@override
  void initInstances() {
    super.initInstances();
    _instance = this;
    /// 设备手势事件回调,
    platformDispatcher.onPointerDataPacket = _handlePointerDataPacket;
  }
  void _handlePointerDataPacket(ui.PointerDataPacket packet) {
    try {
    /// 1.通过`PointerEventConverter.expand` 将 事件(aPointerDataPacket.data)转化为 Flutter 的逻辑事件,以便在处理事件时可以不用考虑不同设备带来的影响。
      _pendingPointerEvents.addAll(
        PointerEventConverter.expand(packet.data, _devicePixelRatioForView),
      );
      if (!locked) {
         /// 2.处理事件
        _flushPointerEventQueue();
      }
    } catch (error, stack) {
      ...
    }
  }
  void _flushPointerEventQueue() {
    while (_pendingPointerEvents.isNotEmpty) {
      handlePointerEvent(_pendingPointerEvents.removeFirst());
    }
  }
/// 创建每个指针的命中测试结果并储存到_hitTests中
/// 注意的是:设备的上的每个动作都会被捕获并保存进来
void _handlePointerEventImmediately(PointerEvent event) {
    HitTestResult? hitTestResult;
    if (event is PointerDownEvent ||
        event is PointerSignalEvent ||
        event is PointerHoverEvent ||
        event is PointerPanZoomStartEvent) {
      hitTestResult = HitTestResult();
      /// 因为 mixin RendererBinding on GestureBinding ...,且在RendererBinding 也实现了 `hitTestInView` 方法
      /// WidgetFlutterBinding 混入的顺序是 `GestureBinding`,再 `RendererBinding`
      /// 所以这里会先调用 RendererBinding 的 `hitTestInView` 再调用 GestureBinding 的`hitTestInView`
      /// RendererBinding的`hitTestInView`方法多了这个代码 `_viewIdToRenderView[viewId]?.hitTest(result, position: position);`
      /// RenderView及其child添加HitTestResult到首先响应手势的event中
      hitTestInView(hitTestResult, event.position, event.viewId);
      if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
        _hitTests[event.pointer] = hitTestResult;
      }
    } else if (event is PointerUpEvent ||
        event is PointerCancelEvent ||
        event is PointerPanZoomEndEvent) {
      hitTestResult = _hitTests.remove(event.pointer);
    } else if (event.down || event is PointerPanZoomUpdateEvent) {
      hitTestResult = _hitTests[event.pointer];
    }
    if (hitTestResult != null ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) {
      /// 分发事件
      dispatchEvent(event, hitTestResult);
    }
  }
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
    assert(!locked);
    if (hitTestResult == null) {
      pointerRouter.route(event);
      return;
    }
    for (final HitTestEntry entry in hitTestResult.path) {
     entry.target.handleEvent(event.transformed(entry.transform), entry);
    }
  }
  
  void hitTestInView(HitTestResult result, Offset position, int viewId) {
    /// 这个`this`就是GestureBinding
    result.add(HitTestEntry(this));
  }
   
  /// 因为GestureBinding的 hitTestInView 方法里面,会创建一个target为GestureBinding 的 HitTestEntry,`dispatchEvent`最终会调用此方法
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);
    if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
      gestureArena.sweep(event.pointer);
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
  }

/// `GestureArenaManager`处理事件竞争,
void sweep(int pointer) {
    final _GestureArena? state = _arenas[pointer];
    if (state == null) {
      return; 
    }
    assert(!state.isOpen);
    if (state.isHeld) {
      state.hasPendingSweep = true;
      return; 
    }
    _arenas.remove(pointer);
    if (state.members.isNotEmpty) {
      // First member wins.
      // 这个first就是触发相应的GestureRecognizer,然后由这个GestureRecognizer去触发对应的方法
      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);
      }
    }
  }

总结:

  1. 与RendererBinding的协作,获取渲染树进行命中测试,坐标变换和事件分发;
  2. 通过GestureDetector等Widget接收手势事件,支持各种内置手势识别器;
  3. GestureBinding是Flutter输入系统的核心,它确保了从底层硬件输入到上层Widget手势事件的完整处理链路;

补充:


/// 这是 RenderView中 hitTest 的处理
bool hitTest(HitTestResult result, {required Offset position}) {
  if (child != null) {
    /// 判断子节点是否需要添加事件
    /// 此方法会对 child 进行层级遍历,对于命中的 child 添加一个 BoxHitTestEntry 节点
    /// child为 RenderBox 类型
    child!.hitTest(BoxHitTestResult.wrap(result), position: position);
  }

  /// 添加一个HitTestEntry节点
  result.add(HitTestEntry(this));
  return true;
}

/// child为 RenderBox 类型,这是 RenderBox 中 hitTest 方法,这是一个父类。
/// 条件方法:hitTestChildren 会被子类重写,
/// 在子类里面 hitTestChildren 基本也都会去调用其 hitTest 方法,这就实现了事件的冒泡,在最上层的节点,会被 result.add 加到第一位。
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;
}

1754228764138.jpg (图片是从网上大佬那里获得的)