Flutter事件分发

   大道同源, 其实Flutter的事件分发跟安卓类似。 下面就详细介绍下Flutter的事件分发体系:

1、 Flutter所有事件源头是 hooks.dart文件的_dispatchPointerDataPacket函数, 接收屏幕的点击、滑动等等各种事件。 类似于安卓的ViewRootImpl.java接收native层的数据。

2、gestures/binding.dart中的GestureBinding是Flutter事件分发的基类, 它的成员函数dispatchEvent、handleEvent和hitTest是核心函数, 从事件队列里按照先入先出方式处理PointerEvent。 处理_dispatchPointerDataPacket发来的数据。

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

3、converter.dart将物理坐标_dispatchPointerDataPacket收到的物理数据PointerDataPacket转换成PointerEvent, 类似于安卓在ViewRootImpl.java将InputEventReceiver收到的InputEvent转换为MotionEvent。

4、recognizer.dart的GestureRecognizer是所有手势识别的基类,

5、rendering/binding.dart的RendererBinding类关联了render树和Flutter引擎, 暂且理解为安卓的Surface。

6、view.dart的RenderView是render树的根节点, 可以理解为安卓的DecorView。

 

下面详细介绍事件的分发流程:

1、Flutter框架初始化会调用runApp方法, 它的作用是初始化各个binging;其中GestureBinding就是处理事件分发的。 attachRootWidget就是设置根节点, 可以看到真正的根节点是renderview, 也是Flutter事件分发的起点。

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(
    container: renderView,
    debugShortDescription: '[root]',
    child: rootWidget
  ).attachToRenderTree(buildOwner, renderViewElement);
}

  2、下面看GestureBinding的逻辑。

void _handlePointerEvent(PointerEvent event) {
    assert(!locked);
    HitTestResult result;
    if (event is PointerDownEvent) {
      assert(!_hitTests.containsKey(event.pointer));
      result = 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);
  }

当收到PointerDownEvent时会创建一个HitTestResult实例(跟安卓判断ACTION_DOWN是一个道理,即点击事件都是从DOWN开始的)。 event.position并不是位置,而是类似于数据库主键,区分不同事件的唯一标识, 在这里用_hitTests的key值。

  final Map _hitTests = {};

/// The result of performing a hit test.
class HitTestResult {
  /// Creates a hit test result.
  ///
  /// If the [path] argument is null, the [path] field will be initialized with
  /// and empty list.
  HitTestResult({ List path })
    : _path = path ?? [];

  /// An unmodifiable list of [HitTestEntry] objects recorded during the hit test.
  ///
  /// The first entry in the path is the most specific, typically the one at
  /// the leaf of tree being hit tested. Event propagation starts with the most
  /// specific (i.e., first) entry and proceeds in order through the path.
  Iterable get path => _path;
  final List _path;

  /// Add a [HitTestEntry] to the path.
  ///
  /// The new entry is added at the end of the path, which means entries should
  /// be added in order from most specific to least specific, typically during an
  /// upward walk of the tree being hit tested.
  void add(HitTestEntry entry) {
    _path.add(entry);
  }

  @override
  String toString() => 'HitTestResult(${_path.isEmpty ? "" : _path.join(", ")})';
}

  _path保存了PointerEvent的传递路径, 一般_path的第一个HitTestEntry是叶子节点。   

  当PointerEvent是Up、Cancel时才会调用dispatchEvent函数。 可以看到Flutter是按照插入到path的先后顺序执行handleEvent,即先进先出。 

 void dispatchEvent(PointerEvent event, HitTestResult result) {
    assert(!locked);
    assert(result != null);
    for (HitTestEntry entry in result.path) {
      try {
        entry.target.handleEvent(event, entry);
      }

再看hitTest函数是把this添加到path里了, 即GestureBinging也会处理点击事件。

 void hitTest(HitTestResult result, Offset position) {
    result.add(HitTestEntry(this));
  }
 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);
    }
  }
}

注册事件监听需要hitTest函数, 而Flutter树节点RenderView依赖RenderBox。

class RenderView extends RenderObject with RenderObjectWithChildMixin {
  /// Creates the root of the render tree.
  ///
  /// Typically created by the binding (e.g., [RendererBinding]).
  ///
  /// The [configuration] must not be null.
  RenderView({
    RenderBox child,

重点看RenderBox的hitTest方法,如果子节点或自己能处理事件则添加到result的path中。 

bool hitTest(HitTestResult result, { @required Offset position }) {
    ...
    if (_size.contains(position)) {
      if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
        result.add(BoxHitTestEntry(this, position));
        return true;
      }
    }
    return false;
  }

 

总结:Flutter事件分发可以理解为注册、处理等2个过程, 相当于按照观察者模式先进先出的处理事件。 

HitTestResult的path顺序(处理事件)一般是 叶子节点---父节点---RenderView---WidgetFlutterBinding.

 Flutter事件分发的关键点

1、将所有监听该事件的观察者添加的HitTestResult的path里;

2、然后按照FIFO方式从path里逐个执行handleEvent。 如果父子widget都监听同一个事件, 那么子widget先执行handleEvent, 但不阻碍父widget执行handleEvent。 这跟安卓是有区别的。

 

你可能感兴趣的:(Flutter)