概述

不论是原生Android、iOS还是JavaScript,只有是波及手势交互都会有事件的散发解决。和原生Android、iOS的事件散发的步骤和原理一样,Flutter的事件散发总体也由手势触发、拦挡和响应等几个局部形成。Flutter所有事件源头是 hooks.dart文件的_dispatchPointerDataPacket函数,通过拦挡屏幕的点击、滑动等各种事件,进而分发给原生代码进行响应(ps:Android事件散发)。

如果你看过理解原生Android、iOS的事件散发机制,那么Flutter的事件散发,其实是在Android和iOS上加了壳,即Flutter的事件散发是在原生Android、iOS的的事件散发上进行包装的(Android - C - Dart,iOS- C -Dart)。其中,C是Flutter的底层engine,负责Flutter下层和原生Android、iOS零碎的交互。

事件散发到Dart的入口类是GestureBinding类,此类位于gestures/binding.dart文件中,与手势辨认相干的都位于gestures包中,如下图所示。

  • converter.dart将物理坐标_dispatchPointerDataPacket收到的物理数据PointerDataPacket转换成PointerEvent, 相似于安卓在ViewRootImpl.java将InputEventReceiver收到的InputEvent转换为MotionEvent。
  • recognizer.dart的GestureRecognizer是所有手势辨认的基类。
  • rendering/binding.dart的RendererBinding类关联了render树和Flutter引擎,等价于安卓的Surface。
  • view.dart的RenderView是render树的根节点,等价于安卓的DecorView。

Flutter的事件散发基类是GestureBinding,关上GestureBinding类,它的成员函数包含dispatchEvent、handleEvent和hitTes等,次要是从事件队列里依照先入先出形式解决PointerEvent,源码如下。

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

其中,WidgetsFlutterBinding.ensureInitialized()函数的作用就是初始化各个binging。

Flutter 事件散发

和Android、iOS相似,Flutter的事件散发的入口在runApp函数,相干的代码如下。

void runApp(Widget app) {  WidgetsFlutterBinding.ensureInitialized()    ..attachRootWidget(app)    ..scheduleWarmUpFrame();}class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {  static WidgetsBinding ensureInitialized() {    if (WidgetsBinding.instance == null)      WidgetsFlutterBinding();    return WidgetsBinding.instance;  }} void attachRootWidget(Widget rootWidget) {  _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(    container: renderView,    debugShortDescription: '[root]',    child: rootWidget  ).attachToRenderTree(buildOwner, renderViewElement);}

WidgetsFlutterBinding.ensureInitialized()函数的作用是初始化各个binging。事实上,Flutter 中的 WidgetsFlutterBinding的 Binding能够分为GestureBinding、ServicesBinding、SchedulerBinding、PaintingBinding、SemanticsBinding、RendererBinding、WidgetsBinding 等 7 种 Binding,它们都有本人在性能上的划分。其中,GestureBinding就是处理事件散发的,attachRootWidget就是设置根节点, 能够看到真正的根节点是renderview, 也是Flutter事件散发的终点。

上面咱们来重点看一下GestureBinding类。

GestureBinding

和Android事件处理的流程一样,首先,零碎会拦挡用户的事件,而后在应用GestureBinding的_handlePointerEvent进行事件命中解决。原生事件达到Dart层之后调用的第一个办法是_handlePointerDataPacket,它的源码如下。

 void _handlePointerDataPacket(ui.PointerDataPacket packet) {    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));     if (!locked)      _flushPointerEventQueue();  }

_handlePointerDataPacket办法有一个PointerEventConverter类,作用是将原生传来的手势数据全副转化为Dart对应的对象保留数据,而后保留到汇合中进行贮存。接下来来咱们看一下_flushPointerEventQueue办法,源码如下。

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

_flushPointerEventQueue办法的作用就是循环解决每个手指的的事件,并进行解决,源码如下。

void _handlePointerEvent(PointerEvent event) {    assert(!locked);    HitTestResult hitTestResult;    //如果是手指按下的话    if (event is PointerDownEvent || event is PointerSignalEvent) {      assert(!_hitTests.containsKey(event.pointer));      hitTestResult = HitTestResult();      //失去碰撞的控件组      hitTest(hitTestResult, event.position);      if (event is PointerDownEvent) {        _hitTests[event.pointer] = hitTestResult;      }      assert(() {        if (debugPrintHitTestResults)          debugPrint('$event: $hitTestResult');        return true;      }());    }    //手指抬起    else if (event is PointerUpEvent || event is PointerCancelEvent) {      hitTestResult = _hitTests.remove(event.pointer);    }    //缓存点击的事件,接下来产生滑动的时候间接复用原来的碰撞控件组    else if (event.down) {      // Because events that occur with the pointer down (like      // PointerMoveEvents) should be dispatched to the same place that their      // initial PointerDownEvent was, we want to re-use the path we found when      // the pointer went down, rather than do hit detection each time we get      // such an event.      hitTestResult = _hitTests[event.pointer];    }    assert(() {      if (debugPrintMouseHoverEvents && event is PointerHoverEvent)        debugPrint('$event');      return true;    }());    if (hitTestResult != null ||        event is PointerHoverEvent ||        event is PointerAddedEvent ||        event is PointerRemovedEvent) {      dispatchEvent(event, hitTestResult);    }  }

这个办法的次要目标就是失去HitTestResult,就是依据按下的坐标地位找出view树中哪些控件在点击的范畴内,手指在挪动和抬起的时候都复用以后的事件,区别在于不同的手指有不同的索引值。接下来,看一下用户的触摸行为,hitTest首先会进入RendererBinding解决,关上RendererBinding类的hitTest办法,如下所示。

RenderView get renderView => _pipelineOwner.rootNode as RenderView;void hitTest(HitTestResult result, Offset position) {    assert(renderView != null);    renderView.hitTest(result, position: position);    super.hitTest(result, position);  }

其中,RenderView能够了解为Flutter 视图树的根View,在Flutter中也叫做Widget ,一个Widget 对应一个Element 。在Flutter中,渲染会三棵树,即Widget 树、Element 树和RenderObject 树。咱们进行页面布局剖析时,就能够看到它们,如下所示。

对于Widget 树、Element 树和RenderObject 树,能够查看Flutter渲染之Widget、Element 和 RenderObject的介绍。

而后,咱们关上renderView.hitTest办法,对应的代码如下所示。

 bool hitTest(HitTestResult result, { Offset position }) {    if (child != null)      child.hitTest(BoxHitTestResult.wrap(result), position: position);    result.add(HitTestEntry(this));    return true;  }

能够看到,根视图是先从子view开始放进汇合,放完子view再放本人,这和前端JS点击事件冒泡的原理是一样的。并且,只有满足条件子视图才会放到 入RenderBox 的这个办法中。

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

接下来,看一下Stack小部件hitTestChildren的实现,源码如下。

  @override  bool hitTestChildren(BoxHitTestResult result, { Offset position }) {    return defaultHitTestChildren(result, position: position);  }bool defaultHitTestChildren(BoxHitTestResult result, { Offset position }) {    // the x, y parameters have the top left of the node's box as the origin    ChildType child = lastChild;    while (child != null) {      final ParentDataType childParentData = child.parentData;      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;  }

这个办法的作用就是判断蕴含Padding的视图是否在点击范畴内,如果命中,则阻止其余事件持续冒泡。看到此处,咱们大体能够看出,Flutter的事件处理次要是判断点击的坐标知否在控件范畴内,如果在范畴内间接响应,如果不在持续向上冒泡,并且事件是从叶子开始的,也即Web中的事件冒泡。

实现命中解决后,接下来回到事件处理的主流程,即事件派发dispatchEvent,代码位于gestrues/binding外面,源码如下。

 void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {    assert(!locked);    // No hit test information implies that this is a hover or pointer    // add/remove event.这种状况出在指针悬停屏幕上方,微微接触或不接触,是手机敏感而言    if (hitTestResult == null) {      assert(event is PointerHoverEvent || event is PointerAddedEvent || event is PointerRemovedEvent);      try {        pointerRouter.route(event);      } catch (exception, stack) {        FlutterError.reportError(FlutterErrorDetailsForPointerEventDispatcher(          exception: exception,          stack: stack,          library: 'gesture library',          context: ErrorDescription('while dispatching a non-hit-tested pointer event'),          event: event,          hitTestEntry: null,          informationCollector: () sync* {            yield DiagnosticsProperty<PointerEvent>('Event', event, style: DiagnosticsTreeStyle.errorProperty);          },        ));      }      return;    }     for (HitTestEntry entry in hitTestResult.path) {      try {        entry.target.handleEvent(event.transformed(entry.transform), entry);      } catch (exception, stack) {        FlutterError.reportError(FlutterErrorDetailsForPointerEventDispatcher(          exception: exception,          stack: stack,          library: 'gesture library',          context: ErrorDescription('while dispatching a pointer event'),          event: event,          hitTestEntry: entry,          informationCollector: () sync* {            yield DiagnosticsProperty<PointerEvent>('Event', event, style: DiagnosticsTreeStyle.errorProperty);            yield DiagnosticsProperty<HitTestTarget>('Target', entry.target, style: DiagnosticsTreeStyle.errorProperty);          },        ));      }    }  }

此办法最基本的作用是循环事件散发,并以冒泡的模式从底部到散发事件,当事件被命中时,即由以后子节点处理事件,这和Android的事件散发的逻辑是一样的。上面以GestureDetector和Listener来举例事件散发的不同。如果用Listener的话,Listener的组件最终对应的RenderObject是RenderPointerListener,它的监测以后点击是否命中的办法如下。

bool hitTest(BoxHitTestResult result, { 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;

应用Listener嵌套的子组件默认状况下是命中的,很多子部件例如TextImage等,它们的hitTestSelf返回True,如果咱们为Text嵌套了Listener,那么事件散发的时候设计的代码如下所示。

void handleEvent(PointerEvent event, HitTestEntry entry) {    assert(debugHandleEvent(event, entry));    if (onPointerDown != null && event is PointerDownEvent)      return onPointerDown(event);    if (onPointerMove != null && event is PointerMoveEvent)      return onPointerMove(event);    if (onPointerUp != null && event is PointerUpEvent)      return onPointerUp(event);    if (onPointerCancel != null && event is PointerCancelEvent)      return onPointerCancel(event);    if (onPointerSignal != null && event is PointerSignalEvent)      return onPointerSignal(event);  }

如果应用的是GestureDetector的话,build办法会为咱们增加很多解决手势的办法类,如TapGestureRecognizer,通过解决手势辨认后,最终返回的是RawGestureDetector,波及的代码如下。

 final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};     if (      onTapDown != null ||      onTapUp != null ||      onTap != null ||      onTapCancel != null ||      onSecondaryTapDown != null ||      onSecondaryTapUp != null ||      onSecondaryTapCancel != null    ) {      gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(        () => TapGestureRecognizer(debugOwner: this),        (TapGestureRecognizer instance) {          instance            ..onTapDown = onTapDown            ..onTapUp = onTapUp            ..onTap = onTap            ..onTapCancel = onTapCancel            ..onSecondaryTapDown = onSecondaryTapDown            ..onSecondaryTapUp = onSecondaryTapUp            ..onSecondaryTapCancel = onSecondaryTapCancel;        },      );    }     if (onDoubleTap != null) {      gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(        () => DoubleTapGestureRecognizer(debugOwner: this),        (DoubleTapGestureRecognizer instance) {          instance            ..onDoubleTap = onDoubleTap;        },      );    }     if (onLongPress != null ||        onLongPressUp != null ||        onLongPressStart != null ||        onLongPressMoveUpdate != null ||        onLongPressEnd != null) {      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(        () => LongPressGestureRecognizer(debugOwner: this),        (LongPressGestureRecognizer instance) {          instance            ..onLongPress = onLongPress            ..onLongPressStart = onLongPressStart            ..onLongPressMoveUpdate = onLongPressMoveUpdate            ..onLongPressEnd =onLongPressEnd            ..onLongPressUp = onLongPressUp;        },      );    }     if (onVerticalDragDown != null ||        onVerticalDragStart != null ||        onVerticalDragUpdate != null ||        onVerticalDragEnd != null ||        onVerticalDragCancel != null) {      gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(        () => VerticalDragGestureRecognizer(debugOwner: this),        (VerticalDragGestureRecognizer instance) {          instance            ..onDown = onVerticalDragDown            ..onStart = onVerticalDragStart            ..onUpdate = onVerticalDragUpdate            ..onEnd = onVerticalDragEnd            ..onCancel = onVerticalDragCancel            ..dragStartBehavior = dragStartBehavior;        },      );    }     if (onHorizontalDragDown != null ||        onHorizontalDragStart != null ||        onHorizontalDragUpdate != null ||        onHorizontalDragEnd != null ||        onHorizontalDragCancel != null) {      gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(        () => HorizontalDragGestureRecognizer(debugOwner: this),        (HorizontalDragGestureRecognizer instance) {          instance            ..onDown = onHorizontalDragDown            ..onStart = onHorizontalDragStart            ..onUpdate = onHorizontalDragUpdate            ..onEnd = onHorizontalDragEnd            ..onCancel = onHorizontalDragCancel            ..dragStartBehavior = dragStartBehavior;        },      );    }     if (onPanDown != null ||        onPanStart != null ||        onPanUpdate != null ||        onPanEnd != null ||        onPanCancel != null) {      gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(        () => PanGestureRecognizer(debugOwner: this),        (PanGestureRecognizer instance) {          instance            ..onDown = onPanDown            ..onStart = onPanStart            ..onUpdate = onPanUpdate            ..onEnd = onPanEnd            ..onCancel = onPanCancel            ..dragStartBehavior = dragStartBehavior;        },      );    }     if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {      gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(        () => ScaleGestureRecognizer(debugOwner: this),        (ScaleGestureRecognizer instance) {          instance            ..onStart = onScaleStart            ..onUpdate = onScaleUpdate            ..onEnd = onScaleEnd;        },      );    }     if (onForcePressStart != null ||        onForcePressPeak != null ||        onForcePressUpdate != null ||        onForcePressEnd != null) {      gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(        () => ForcePressGestureRecognizer(debugOwner: this),        (ForcePressGestureRecognizer instance) {          instance            ..onStart = onForcePressStart            ..onPeak = onForcePressPeak            ..onUpdate = onForcePressUpdate            ..onEnd = onForcePressEnd;        },      );    }     return RawGestureDetector(      gestures: gestures,      behavior: behavior,      excludeFromSemantics: excludeFromSemantics,      child: child,    );

并且,RawGestureDetector默认应用的也是Listener,它注册了手指按下的办法,散发的时候Down事件是sdk默认解决的。

 void _handlePointerDown(PointerDownEvent event) {    assert(_recognizers != null);    for (GestureRecognizer recognizer in _recognizers.values)      recognizer.addPointer(event);  }

此办法会向Binding路由器中注册那些须要解决的事件,如果咱们只申明了点击事件,那么汇合中负责增加的GestureRecognizer的实现类就是TapGestureRecognizer,接下来咱们看一下addPointer办法。

 void addPointer(PointerDownEvent event) {    _pointerToKind[event.pointer] = event.kind;    if (isPointerAllowed(event)) {      addAllowedPointer(event);    } else {      handleNonAllowedPointer(event);    }  }bool isPointerAllowed(PointerDownEvent event) {    switch (event.buttons) {      case kPrimaryButton:        if (onTapDown == null &&            onTap == null &&            onTapUp == null &&            onTapCancel == null)          return false;        break;      case kSecondaryButton:        if (onSecondaryTapDown == null &&            onSecondaryTapUp == null &&            onSecondaryTapCancel == null)          return false;        break;      default:        return false;    }    return super.isPointerAllowed(event);  }

isPointerAllowed办法的作用就是用来断定以后的手势,默认返回false,如果事件比命中,接下来执行addAllowedPointer办法,如下所示。

void addAllowedPointer(PointerDownEvent event) {    startTrackingPointer(event.pointer, event.transform);    if (state == GestureRecognizerState.ready) {      state = GestureRecognizerState.possible;      primaryPointer = event.pointer;      initialPosition = OffsetPair(local: event.localPosition, global: event.position);      if (deadline != null)        _timer = Timer(deadline, () => didExceedDeadlineWithEvent(event));    }    void startTrackingPointer(int pointer, [Matrix4 transform]) {    GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);    _trackedPointers.add(pointer);    assert(!_entries.containsValue(pointer));    _entries[pointer] = _addPointerToArena(pointer);  }

这两个办法的次要作用就是用来将以后的handleEvent办法增加到GestureBinding路由器外面去,而_addPointerToArena是就是增加处理事件的具体逻辑。接下来,咱们来看一下GestureBinding外面的handleEvent函数的事件散发逻辑。

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

如果手指按下的时候GestureRecognizer的handleEvent办法没有决策出到底哪个控件会成为事件的解决者,那么会执行 gestureArena.close()办法,如下所示。

void close(int pointer) {    final _GestureArena state = _arenas[pointer];    if (state == null)      return; // This arena either never existed or has been resolved.    state.isOpen = false;    assert(_debugLogDiagnostic(pointer, 'Closing', state));    _tryToResolveArena(pointer, state);  }

如果未决策出哪个控件处理事件的时候,state.isOpen此时被标记为false,也即是敞开手势的解决。

void _tryToResolveArena(int pointer, _GestureArena state) {    assert(_arenas[pointer] == state);    assert(!state.isOpen);    if (state.members.length == 1) {      scheduleMicrotask(() => _resolveByDefault(pointer, state));    } else if  (state.members.isEmpty) {      _arenas.remove(pointer);      assert(_debugLogDiagnostic(pointer, 'Arena empty.'));    } else if (state.eagerWinner != null) {      assert(_debugLogDiagnostic(pointer, 'Eager winner: ${state.eagerWinner}'));      _resolveInFavorOf(pointer, state, state.eagerWinner);    }  }

如果手势竞争中,有竞争胜出者,则由胜出者执行事件处理,如下所示。

void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {    assert(state == _arenas[pointer]);    assert(state != null);    assert(state.eagerWinner == null || state.eagerWinner == member);    assert(!state.isOpen);    _arenas.remove(pointer);    //其余的命中全副回绝    for (GestureArenaMember rejectedMember in state.members) {      if (rejectedMember != member)        rejectedMember.rejectGesture(pointer);    }    member.acceptGesture(pointer);  }

如果事件处理中没有具体的事件处理对象,将会默认采纳最底层的的叶子节点控件作为事件处理者,也就是说最内层的那个控件将耗费事件。也就是说,如果应用GestureRecognizer来辨认手势事件时,最终事件会被最内层的GestureRecognizer耗费,这和Android单个控件耗费事件差不多,所以嵌套滚动总是先滚动内层,先被内层耗费,而后再执行外层。

参考: Flutter 事件散发