16

Flutter事件分发源码剖析

 3 years ago
source link: https://segmentfault.com/a/1190000025144623
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

概述

不管是原生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包中,如下图所示。

nEbQziz.png!mobile

  • 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 树。我们进行页面布局分析时,就可以看到它们,如下所示。

qYnYrqq.png!mobile

关于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 事件分发


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK