Flutter事件机制

118 阅读2分钟

文章转自>>

事件分发机制

  1. 命中测试
  2. 事件分发
  3. 事件清理

命中测试

/// A RenderProxyBox subclass that allows you to customize the
/// hit-testing behavior.
abstract class RenderProxyBoxWithHitTestBehavior extends RenderProxyBox{

  @override
  bool hitTest(BoxHitTestResult result, { required Offset position }) {
    bool hitTarget = false;
    if (size.contains(position)) {
      hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
      if (hitTarget || behavior == HitTestBehavior.translucent) {
        result.add(BoxHitTestEntry(this, position));
      }
    }
    return hitTarget;
  }

  @override
  bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque;
  
  ...
}
  • 手指按下触发PointerDownEvent事件,按照深度优先开始进行命中测试
  • 如果子组件命中返回为ture,则先被加入命中结果数组,其父组件同样也会返回true,并加入命中结果数组。这一点在接口RenderProxyBoxWithHitTestBehaviorhitTest方法中体现——hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position))。
  • 这里要注意如果是多个子组件,是如何去判断命中的。这里我们看一下RenderBoxContainerDefaultsMixins的实现:
  bool defaultHitTestChildren(BoxHitTestResult result, { required Offset position }) {
    ChildType? child = lastChild;
    while (child != 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(
        offset: childParentData.offset,
        position: position,
        hitTest: (BoxHitTestResult result, Offset transformed) {
          assert(transformed == position - childParentData.offset);
          return child!.hitTest(result, position: transformed);
        },
      );
      if (isHit) {
        return true;
      }
      child = childParentData.previousSibling;
    }
    return false;
  }

多个子组件,遍历子组件(从后往前遍历),有命中的就终止遍历,返回结果。

为什么从后往前(previousSibling),而不是从前往后(nextSibling)?

自问自答:我个人理解是命中区域会有重叠,如Stack。当Stack有重叠的时候,最上层显示children中靠后的,当然事件也优先让他们去命中测试。

命中测试当前作用手机了命中结果数组HitTestResult

事件分发

GestureBinding类为单例类,集成自BindingBase,并从BindingBase中获取platformDispatcher,并对platformDispatcher的触摸事件处理进行方法进行赋值。 事件分发调用是由platformDispatcher单例去管理的。

ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance

GestureBinding管理Queue,调用_handlePointerEventImmediately并在合适时机遍历分发event到目标上,包括hitTest命中测试方法,

dispatchEvent方法先判断事件类型,如果HitTestResult有结果,遍历调用HitTestTargethandleEvent方法,组件只需要重新handleEvent方法去处理事件就好。对于我们日常用的ListenerGestureDetector(Listener的封装)均在ListenercreateRenderObject方法返回的RenderPointerListener中实现:

  @override
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    assert(debugHandleEvent(event, 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 PointerPanZoomStartEvent) {
      return onPointerPanZoomStart?.call(event);
    }
    if (event is PointerPanZoomUpdateEvent) {
      return onPointerPanZoomUpdate?.call(event);
    }
    if (event is PointerPanZoomEndEvent) {
      return onPointerPanZoomEnd?.call(event);
    }
    if (event is PointerSignalEvent) {
      return onPointerSignal?.call(event);
    }
  }

handleEvent根据具体的事件类型,调用传入的回调方法。

至此事件完整的分发出去了。

事件清理

事件清理也是在_handlePointerEventImmediately方法中进行,当收到upcancel等事件后,进行相应的分发,并从hitTestResult中移除

记录个人学习过程,不对的地方欢迎大佬指正