Flutter笔记-事件分发

作者: 叶落清秋 | 来源:发表于2019-02-14 15:35 被阅读35次

    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', <String, String>{});
        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<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();
    
      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<int, HitTestResult> _hitTests = <int, HitTestResult>{};
    
      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<RenderObjectWidget> parentDataElement = _findAncestorParentDataElement();
        if (parentDataElement != null)
          _updateParentData(parentDataElement.widget);
      }
    

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

    @override
      void insertChildRenderObject(RenderObject child, dynamic slot) {
        //指定是RenderObjectWithChildMixin类型,和前面对应上了
        final RenderObjectWithChildMixin<RenderObject> 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>[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. 理一理

    理一下顺序:


    分配流程

    3. 总结

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

    下一篇:深入分析GestureDetector

    相关文章

      网友评论

        本文标题:Flutter笔记-事件分发

        本文链接:https://www.haomeiwen.com/subject/hjkdeqtx.html