Flutter笔记-事件分发

ps: 文中flutter源码版本 1.0.0


1. 手势分配流程

我们从头开始分析,先看runApp(rootWidget):

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}

ensureInitialized()进行了一系列绑定,包含了手势

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    //不存在则会创建一个
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}

这是一个典型的单例模式,调用构造函数,然后调用父类的构造函数

abstract class BindingBase {
  BindingBase() {
    developer.Timeline.startSync('Framework initialization');
    assert(!_debugInitialized);
    initInstances();
    assert(_debugInitialized);
    assert(!_debugServiceExtensionsRegistered);
    initServiceExtensions();
    assert(_debugServiceExtensionsRegistered);
    developer.postEvent('Flutter.FrameworkInitialization', {});
    developer.Timeline.finishSync();
  }
  ...
}

构造函数中会调用initInstances()方法,那么这个方法在哪实现的?
关键点在于with(dart语法,混合),重复的属性或方法取最后的mixin类,即initInstances()和instance等方法和属性取WidgetsBinding中的

mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    //注,这个super是找的上一级混合类
    super.initInstances();
    _instance = this;
    buildOwner.onBuildScheduled = _handleBuildScheduled;
    ui.window.onLocaleChanged = handleLocaleChanged;
    ui.window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    SystemChannels.system.setMessageHandler(_handleSystemMessage);
  }

  static WidgetsBinding get instance => _instance;
  static WidgetsBinding _instance;
  ...
}

通过super.initInstances(),会逐渐往前调用,简单理解就是所有混合类的initInstances()都将被调用
最终会调用我们所要找的手势绑定类

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

  @override
  void unlocked() {
    super.unlocked();
    _flushPointerEventQueue();
  }

  static GestureBinding get instance => _instance;
  static GestureBinding _instance;

  final Queue _pendingPointerEvents = Queue();

  void _handlePointerDataPacket(ui.PointerDataPacket packet) {
 _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, ui.window.devicePixelRatio));
    if (!locked)
     //2.刷新手势事件队列
      _flushPointerEventQueue();
  }
  ...
  void _flushPointerEventQueue() {
    assert(!locked);
    //3. 处理手势事件
    while (_pendingPointerEvents.isNotEmpty)
      _handlePointerEvent(_pendingPointerEvents.removeFirst());
  }

  
  final Map _hitTests = {};

  void _handlePointerEvent(PointerEvent event) {
    assert(!locked);
    HitTestResult result;
    if (event is PointerDownEvent) {
      assert(!_hitTests.containsKey(event.pointer));
      result = HitTestResult();
      //4. 手势添加到测试result列表中
      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; 
    }
    if (result != null)
      //5.分发事件
      dispatchEvent(event, result);
  }

  
  @override 
  void hitTest(HitTestResult result, Offset position) {
    //WidgetsFlutterBinding调用时添加到result中
    result.add(HitTestEntry(this));
  }

  @override 
  void dispatchEvent(PointerEvent event, HitTestResult result) {
    assert(!locked);
    assert(result != null);
    //只有在result列表中才会进行事件处理
    for (HitTestEntry entry in result.path) {
      try {
        //6. 处理事件,加入了列表,包装了一层
        entry.target.handleEvent(event, entry);
      } catch (exception, stack) {
        //处理异常错误,忽略
       ...
      }
    }
  }
  //WidgetsFlutterBinding默认调用
  @override 
  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);
    }
  }
}

按着注释顺序逐个分析下来,在第四步时,如果不注意,可能就会犯错
回到最开始,WidgetsFlutterBinding混合了许多方法,其中的 RendererBinding混合了HitTestable,重写了hitTest(HitTestResult result, Offset position)方法,所以这的hitTest是使用RendererBinding中的而非GestureBinding的

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    ui.window
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    addPersistentFrameCallback(_handlePersistentFrameCallback);
  }
 
  void initRenderView() {
    assert(renderView == null);
    renderView = RenderView(configuration: createViewConfiguration());
    renderView.scheduleInitialFrame();
  }
  ...
  @override
  void hitTest(HitTestResult result, Offset position) {
    assert(renderView != null);
    //唯一的区别是使用了renderView中的hitTest
    //这么写,猜猜也知道肯定要去RenderView寻找答案
    renderView.hitTest(result, position: position);
    //当上面遍历完后,仍然会调用GestureBinding中的hitTest
    super.hitTest(result, position); 
  }
  ...
}

renderView在initInstances中创建,实际使用中,初始化时是无手势的,真正进行变化的在unlocked() 方法中(同步锁,用于处理手势事件)
所以,这里又回到了RenderView的hitTest方法中来

bool hitTest(HitTestResult result, { Offset position }) {
    //child的类型是RenderBox(即RenderObject)
    if (child != null)
      child.hitTest(result, position: position);
    result.add(HitTestEntry(this));
    return true;
  }

HitTestEntry是什么,其实就主要包含一个HitTestTarget,也就是handleEvent(PointerEvent event, HitTestEntry entry)方法的抽象类

class HitTestEntry {
  const HitTestEntry(this.target);
  final HitTestTarget target;
  @override
  String toString() => '$target';
}

回到之前第六步,entry.target.handleEvent(event, entry)中的target也是一个RenderView,而child.hitTest(result, position: position)是这样的

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

这也是为什么要重写hitTestChildren()或hitTestSelf(position)的原因,当他们都为false时,result就不会添加这个控件,即事件分发不会分配到该控件上。

2. 手势控件分析

分析完流程,再看看手势监听的控件

前面我们知道,常用手势控件有Listener和GestureDetector,后者是对前者的封装,这里对基础手势简单分析下

我们按照以下的结构进行分析,手势监听控件里添加一个文本

Listener(
  child: Text("这是一个测试"),
)

a. Listener中的流程

逐一分析,先分析Listener:

class Listener extends SingleChildRenderObjectWidget {
  const Listener({
    Key key,
    this.onPointerDown,
    this.onPointerMove,
    this.onPointerUp,
    this.onPointerCancel,
    this.behavior = HitTestBehavior.deferToChild,
    Widget child
  }) : assert(behavior != null),
       super(key: key, child: child);

  final PointerDownEventListener onPointerDown;
  final PointerMoveEventListener onPointerMove;
  final PointerUpEventListener onPointerUp;
  final PointerCancelEventListener onPointerCancel;
  final HitTestBehavior behavior;

  @override
  RenderPointerListener createRenderObject(BuildContext context) {
    return RenderPointerListener(
      onPointerDown: onPointerDown,
      onPointerMove: onPointerMove,
      onPointerUp: onPointerUp,
      onPointerCancel: onPointerCancel,
      behavior: behavior
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderPointerListener renderObject) {
    renderObject
      ..onPointerDown = onPointerDown
      ..onPointerMove = onPointerMove
      ..onPointerUp = onPointerUp
      ..onPointerCancel = onPointerCancel
      ..behavior = behavior;
  }
 ...
}

直接查看RenderPointerListener源码,这里传递了几个回调方法

class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
  RenderPointerListener({
    this.onPointerDown,
    this.onPointerMove,
    this.onPointerUp,
    this.onPointerCancel,
    HitTestBehavior behavior = HitTestBehavior.deferToChild,
    RenderBox child
  }) : super(behavior: behavior, child: child);

  PointerDownEventListener onPointerDown;
  PointerMoveEventListener onPointerMove;
  PointerUpEventListener onPointerUp;
  PointerCancelEventListener onPointerCancel;

  @override
  void performResize() {
    size = constraints.biggest;
  }

  @override
  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);
  }
  ...
}

这里有handleEvent方法,符合了之前的猜测,然后使用回调方法处理事件
继续往下,看其子类,RenderProxyBoxWithHitTestBehavior中有hitTest方法

@override
  bool hitTest(HitTestResult result, { Offset position }) {
    bool hitTarget = false;
    if (size.contains(position)) {
      hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
      //要往result中添加数据需要满足3个条件中任意一个即可
     //1. hitTestChildren为true,重写了,看下面说明(默认是false)
     //2. hitTestSelf为true,并未重写(默认是false)
     //3. behavior为translucent(默认类型是deferToChild)
      if (hitTarget || behavior == HitTestBehavior.translucent)
        result.add(BoxHitTestEntry(this, position));
    }
    return hitTarget;
  }

接着再在其子类RenderProxyBoxMixin中找到了hitTestChildren方法:

@override
  bool hitTestChildren(HitTestResult result, { Offset position }) {
    return child?.hitTest(result, position: position) ?? false;
  }

b. child值获取过程

child?.hitTest(result, position: position),这的child是什么?

child位于RenderObjectWithChildMixin(RenderPointerListener继承的RenderProxyBox的混合类)中,是一个RenderObject,直接猜测的话应该就是我们传的Text控件

那么并未通过构造函数传值,值如何获取到的呢?

之前我们知道,控件都需要经过build过程,通过rebuild()接着执行performRebuild()

  @override
  void performRebuild() {
    //断言判断和错误处理省略
    ...
    Widget built;
    try {
      //实际上这就是StatelessWidget.build或State.build
      built = build();
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      ...
    } finally {
      ...
    }
    try {
      //这个built即是后面的newWidget
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      ...
    }
  ...
  }

更新子孩子

  @protected
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    ...
    return inflateWidget(newWidget, newSlot);
  }

@protected
  Element inflateWidget(Widget newWidget, dynamic newSlot) {
    ...
    final Element newChild = newWidget.createElement();
    //登记
    newChild.mount(this, newSlot);
    assert(newChild._debugLifecycleState == _ElementLifecycle.active);
    return newChild;
  }

Element中的createElement()是一个抽象方法,我们寻找他的实现类SingleChildRenderObjectElement(因为Listener是一个SingleChildRenderObjectWidget)

@override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    //向下子控件遍历
    _child = updateChild(_child, widget.child, null);
  }

这里有个循环,不断遍历下去,直到无子类控件,我们看看父类的mount做了什么?

//RenderObjectElement中mount
 @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    //关键,widget如果是Listener,_renderObject则是返回的RenderPointerListener,基类方法,通用的
    _renderObject = widget.createRenderObject(this);
    assert(() { _debugUpdateRenderObjectOwner(); return true; }());
    assert(_slot == newSlot);
   //关联object对象
    attachRenderObject(newSlot);
    _dirty = false;
  }
//RenderObjectElement中attachRenderObject
@override
  void attachRenderObject(dynamic newSlot) {
    assert(_ancestorRenderObjectElement == null);
    _slot = newSlot;
    //找到父控件的RenderObjectElement,因为都是单孩子控件,所以也是SingleChildRenderObjectElement
    //父类的添加在 inflateWidget,这里并不详述
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    //单从单词意思上就能猜到是这个了   
 _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
    final ParentDataElement parentDataElement = _findAncestorParentDataElement();
    if (parentDataElement != null)
      _updateParentData(parentDataElement.widget);
  }

RenderObjectElement中的insertChildRenderObject是一个抽象类,我们再次回到SingleChildRenderObjectElement

@override
  void insertChildRenderObject(RenderObject child, dynamic slot) {
    //指定是RenderObjectWithChildMixin类型,和前面对应上了
    final RenderObjectWithChildMixin renderObject = this.renderObject;
    assert(slot == null);
    assert(renderObject.debugValidateChild(child));
    //终于找到了,给child赋值了
    renderObject.child = child;
    assert(renderObject == this.renderObject);
  }

c. Text中手势分析

前面推测出child是一个RenderObject,通过widget.createRenderObject(this)返回的,但是Text是一个StatelessWidget,并没有createRenderObject方法

大胆的假设一下,内部肯定间接的实现了一个RenderObject类

来看源码:

class Text extends StatelessWidget {
  ...

  @override
  Widget build(BuildContext context) {
    final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
    TextStyle effectiveTextStyle = style;
    if (style == null || style.inherit)
      effectiveTextStyle = defaultTextStyle.style.merge(style);
    if (MediaQuery.boldTextOverride(context))
      effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));
    //内部使用的是 RichText
    Widget result = RichText(
      textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
      textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
      locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
      softWrap: softWrap ?? defaultTextStyle.softWrap,
      overflow: overflow ?? defaultTextStyle.overflow,
      textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
      maxLines: maxLines ?? defaultTextStyle.maxLines,
      text: TextSpan(
        style: effectiveTextStyle,
        text: data,
        children: textSpan != null ? [textSpan] : null,
      ),
    );
    if (semanticsLabel != null) {
      result = Semantics(
        textDirection: textDirection,
        label: semanticsLabel,
        child: ExcludeSemantics(
          child: result,
        )
      );
    }
    return result;
  }
  ...
}

查看RichText:

class RichText extends LeafRenderObjectWidget {
  //找到了该方法
  @override
  RenderParagraph createRenderObject(BuildContext context) {
    assert(textDirection != null || debugCheckHasDirectionality(context));
    return RenderParagraph(text,
      textAlign: textAlign,
      textDirection: textDirection ?? Directionality.of(context),
      softWrap: softWrap,
      overflow: overflow,
      textScaleFactor: textScaleFactor,
      maxLines: maxLines,
      locale: locale ?? Localizations.localeOf(context, nullOk: true),
    );
}

符合之前的假设,里面真创建了RenderObject对象

class RenderParagraph extends RenderBox {
  ...
  //自身可以点击
  @override
  bool hitTestSelf(Offset position) => true;
  //重写了事件处理方式
  @override
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    assert(debugHandleEvent(event, entry));
    if (event is! PointerDownEvent)
      return;
    _layoutTextWithConstraints(constraints);
    final Offset offset = entry.localPosition;
    final TextPosition position = _textPainter.getPositionForOffset(offset);
    final TextSpan span = _textPainter.text.getSpanForPosition(position);
    span?.recognizer?.addPointer(event);
  }
  ...
}

RenderParagraph使用的是RenderBox中的hitTest方法

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

hitTestSelf通过,会将RenderParagraph加入列表中,同时返回true,然后父类也会添加到列表中,这样都会接收到分发的事件

d. 理一理

理一下顺序:


Flutter笔记-事件分发_第1张图片
分配流程

3. 总结

hitTestChildren:点击事件传给子控件
hitTestSelf:自己接收到事件
handleEvent:处理事件

下一篇:深入分析GestureDetector

你可能感兴趣的:(Flutter笔记-事件分发)