浅谈Flutter核心机制之--- 事件分发

Posted 一叶飘舟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈Flutter核心机制之--- 事件分发相关的知识,希望对你有一定的参考价值。

前言

在Flutter中统的事件通过Engine层发送到framework层,GestureBinding负责framework层事件的处理,接下来由GestureBinding为入口来进行分析。

设备事件信号分类

一、Flutter对设备事件定义了touch 、mouse、stylus 、invertedStylus、unknown几种,其中手机端屏幕触摸产生的事件对应的是touch类型。其他还有手写笔 , 鼠标 等设备。

enum PointerDeviceKind 
  /// A touch-based pointer device.
  touch,
  /// A mouse-based pointer device.
  mouse,
  /// A pointer device with a stylus.
  stylus,
  /// A pointer device with a stylus that has been inverted.
  invertedStylus,
  /// An unknown pointer device.
  unknown

二、以上事件源的数据通过PointerEventConverter#expand方法转换,最终体现到Flutter业务端的事件都会抽象成PointerEvent

  void _handlePointerDataPacket(ui.PointerDataPacket packet) 
    // We convert pointer data to logical pixels so that e.g. the touch slop can be
    // defined in a device-independent manner.
    1,转换数据成PointerEvent
    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
    if (!locked)
    2,处理事件
      _flushPointerEventQueue();
  

事件类型

Flutter中对屏幕触发的事件定义了以下类型:

  • PointerAddedEvent : 接触屏幕(获取事件,创建了事件追踪器)
  • PointerHoverEvent :悬停事件(前面已经创建了事件追踪器,再次使用事件追踪器,与PointerAddedEvent互斥)
  • PointerDownEvent :按下事件(发生在PointerAddedEvent或PointerHoverEvent之后)
  • PointerMoveEvent :滑动事件
  • PointerUpEvent : 完全离开屏幕事件
  • PointerCancelEvent : 事件取消(事件追踪器被断开,比如正在滑动时弹出Dialog焦点变化导致事件取消,与PointerAddedEvent成对)
  • PointerRemovedEvent : 事件可能已偏离设备的检测范围,或者可能已完全与系统断开连接。

三.处理事件刷新事件队列

  void _flushPointerEventQueue() 
    assert(!locked);
    //处理队列里面所有的事件
    while (_pendingPointerEvents.isNotEmpty)
      handlePointerEvent(_pendingPointerEvents.removeFirst());
  
  void handlePointerEvent(PointerEvent event) 
    ...
    _handlePointerEventImmediately(event);
  
  void _handlePointerEventImmediately(PointerEvent event) 
    HitTestResult? hitTestResult;
 1.  if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) 
      assert(!_hitTests.containsKey(event.pointer));
      hitTestResult = HitTestResult();
 2.    hitTest(hitTestResult, event.position);
      if (event is PointerDownEvent) 
        _hitTests[event.pointer] = hitTestResult;
      
​
3.  else if (event is PointerUpEvent || event is PointerCancelEvent) 
      hitTestResult = _hitTests.remove(event.pointer);
4. else if (event.down) 
      // Because events that occur with the pointer down (like
      // [PointerMoveEvent]s) 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];
    
​
    if (hitTestResult != null ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) 
 
5.      dispatchEvent(event, hitTestResult);
    
  
  1. down和hover事件会创建一个HitTestResult,如果是down事件用集合存起来(与后续的move,up事件进行完整的事件分发)

  2. 这里的从hitTest方法被RendererBinding重写了,根部进行hitTest,添加一个根部

      @override
      void hitTest(HitTestResult result, Offset position) 
        assert(renderView != null);
        assert(result != null);
        assert(position != null);
        //renderView对事件进行hitTest冒泡
        renderView.hitTest(result, position: position);
        //调用super添加一个根部的HitTestEntry节点
        super.hitTest(result, position);
      

    renderView.hitTest

     bool hitTest(HitTestResult result,  required Offset position ) 
        if (child != null)
        //对child进行层级遍历hitTest,对命中的会 result.add(HitTestEntry());进行事件处理 
          child!.hitTest(BoxHitTestResult.wrap(result), position: position);
       //对命中的创建一个HitTestEntry进行事件处理   
        result.add(HitTestEntry(this));
        return true;
      
  3. up事件会移除集合中的HitTestResult,此次事件链路结束。

  4. 这里的event.down指的是PointerMoveEvent

  5. dispatchEvent事件分发

  void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) 
    assert(!locked);
    // No hit test information implies that this is a [PointerHoverEvent],
    // [PointerAddedEvent], or [PointerRemovedEvent]. These events are specially
    // routed here; other events will be routed through the `handleEvent` below.
    if (hitTestResult == null) 
      assert(event is PointerAddedEvent || event is PointerRemovedEvent);
      try 
        pointerRouter.route(event);
       catch (exception, stack) 
​
      
      return;
    
​
    for (final HitTestEntry entry in hitTestResult.path) 
      try 
       //调消费事件
        entry.target.handleEvent(event.transformed(entry.transform), entry);
       catch (exception, stack) 
        
          ,
        ));
      
    
  

事件分发流程图

小结

只有命中了才会添加一个HitTestEntry到HitTestResult中,最后接收事件处理执行handleEvent方法,handleEvent方法会把事件再分发给到手势识别器,从上面RenderObject中的hitest方法可以看出优先调用child中的hitest方法,这样可以让事件的优先处理权交给层级最深的child

手势识别事件

GestureBinding中的handleEvent方法是最后一个执行的,它里面主要做了手势事件竞技。

手势识别的成员:

  1. GestureArenaManager 竞技管理者

  2. pointer 一个int值, 从 down 到 up 完整的链路中pointer的值不会变

  3. GestureArenaMember 竞技者(具体的手势识别器,如:TapGestureRecognizer,VerticalDragGestureRecognizer)

  4. _GestureArena 竞技场 一个Pointer只创建一个

  5. GestureArenaEntry 一个组合类,一个Pointer会创建多个记录事件的链路(对应GestureArenaManager.add)

GestureArenaManager

GestureArenaManager是一个管理类全局只创建了一个,它对外只暴露了五个方法:add,sweep,hold,close,release

  1. add :添加一个手势识别器(GestureArenaMember)
  2. close : 关闭手势竞技场,禁止新成员进入
  3. sweep:让_GestureArena(竞技场)中的第一个GestureArenaMember(手势识别器)获胜,当 _GestureArena中hold = true 时则延时让第一个获胜
  4. hold : 标记该point为hold
  5. release:清除hold标记,并且强制让第一个获胜(同sweep)
1
  GestureArenaEntry add(int pointer, GestureArenaMember member) 
    final _GestureArena state = _arenas.putIfAbsent(pointer, () 
      assert(_debugLogDiagnostic(pointer, '★ Opening new gesture arena.'));
      return _GestureArena();
    );
    state.add(member);
    assert(_debugLogDiagnostic(pointer, 'Adding: $member'));
    return GestureArenaEntry._(this, pointer, member);
  
​
2
  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);
  
​
3
  void sweep(int pointer) 
    final _GestureArena? state = _arenas[pointer];
    if (state == null)
      return; // This arena either never existed or has been resolved.
    assert(!state.isOpen);
    if (state.isHeld) 
      state.hasPendingSweep = true;
      assert(_debugLogDiagnostic(pointer, 'Delaying sweep', state));
      return; // This arena is being held for a long-lived member.
    
    assert(_debugLogDiagnostic(pointer, 'Sweeping', state));
    _arenas.remove(pointer);
    if (state.members.isNotEmpty) 
      
      state.members.first.acceptGesture(pointer);
      // Give all the other members the bad news.
      for (int i = 1; i < state.members.length; i++)
        state.members[i].rejectGesture(pointer);
    
  
​
4
  void hold(int pointer) 
    final _GestureArena? state = _arenas[pointer];
    if (state == null)
      return; // This arena either never existed or has been resolved.
    state.isHeld = true;
    assert(_debugLogDiagnostic(pointer, 'Holding', state));
  
​
5
  void release(int pointer) 
    final _GestureArena? state = _arenas[pointer];
    if (state == null)
      return; // This arena either never existed or has been resolved.
    state.isHeld = false;
    assert(_debugLogDiagnostic(pointer, 'Releasing', state));
    if (state.hasPendingSweep)
      sweep(pointer);
  

GestureArenaMember

GestureArenaMember是手势识别器的顶级接口类,在竞技结束后如果当前识别器抢夺成功会调用acceptGesture方法失败则调用rejectGesture方法

abstract class GestureArenaMember 
  /// Called when this member wins the arena for the given pointer id.
  void acceptGesture(int pointer);
​
  /// Called when this member loses the arena for the given pointer id.
  void rejectGesture(int pointer);

复制代码

GestureArenaEntry

记录framework每一次pointer事件的回调,回调一次就创建一个GestureArenaEntry

class GestureArenaEntry 
  GestureArenaEntry._(this._arena, this._pointer, this._member);
​
  final GestureArenaManager _arena;
  final int _pointer;
  final GestureArenaMember _member;
​
  /// Call this member to claim victory (with accepted) or admit defeat (with rejected).
  ///
  /// It's fine to attempt to resolve a gesture recognizer for an arena that is
  /// already resolved.
  void resolve(GestureDisposition disposition) 
    _arena._resolve(_pointer, _member, disposition);
  

_GestureArena

_GestureArena竞技场,里面管理当前pointer所有的GestureArenaMember(竞技者,具体的手势识别器),以及一个获胜者eagerWinner

RenderObject

所有手势识别器竞技都是直接或者间接通过RenderObject#handleEvent方法触发的,前面我们讲了事件分发的流程它会调用GestureRecognizer#addPointer方法,这里以RangeSlider为例子,RangeSlider对应的RenderObject为_RenderRangeSlider

  @override
  void handleEvent(PointerEvent event, HitTestEntry entry) 
    assert(debugHandleEvent(event, entry));
    if (event is PointerDownEvent && isEnabled) 
      // We need to add the drag first so that it has priority.
      _drag.addPointer(event);
      _tap.addPointer(event);
    
  
复制代码

上面的处理方式是优先让滑动事件的手势识别器优先进入竞技场

  1. addPointer
  void addPointer(PointerDownEvent event) 
    _pointerToKind[event.pointer] = event.kind;
    if (isPointerAllowed(event)) 
      addAllowedPointer(event);
     else 
      handleNonAllowedPointer(event);
    
  
复制代码
  1. isPointerAllowed

isPointerAllowed方法会被具体的手势识别器重写,根据具体的业务逻辑判断是否跟踪此事件

  1. PointerRouter#addRoute

    在isPointerAllowed返回true时候最终都会调用PointerRouter#addRoute方法

  @protected
  void startTrackingPointer(int pointer, [Matrix4? transform]) 
   // 添加到路由里面
    GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);
    _trackedPointers.add(pointer);
    assert(!_entries.containsValue(pointer));
    _entries[pointer] = _addPointerToArena(pointer);
  
复制代码
  1. GestureBinding#handleEvent触发路由

    从上面得知最后一个HitTestTarget就是GestureBinding,所以它在事件事件分发的末端

    @override // from HitTestTarget
     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);
       
     
  1. PointerRouter#route调用路由

      void route(PointerEvent event) 
        final Map<PointerRoute, Matrix4?>? routes = _routeMap[event.pointer];
        final Map<PointerRoute, Matrix4?> copiedGlobalRoutes = Map<PointerRoute, Matrix4?>.from(_globalRoutes);
        if (routes != null) 
          _dispatchEventToRoutes(
            event,
            routes,
            Map<PointerRoute, Matrix4?>.from(routes),
          );
        
        _dispatchEventToRoutes(event, _globalRoutes, copiedGlobalRoutes);
      
      
        void _dispatchEventToRoutes(
        PointerEvent event,
        Map<PointerRoute, Matrix4?> referenceRoutes,
        Map<PointerRoute, Matrix4?> copiedRoutes,
      ) 
        copiedRoutes.forEach((PointerRoute route, Matrix4? transform) 
          if (referenceRoutes.containsKey(route)) 
            _dispatch(event, route, transform);
          
        );
      
      
        void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) 
        try 
          event = event.transformed(transform);
          route(event);
         catch (exception, stack)       
        
      
    1. GestureRecognizer#handleEvent

      route对应手势识别器中handleEvent方法

      整体流程如下图:

总结

Flutter事件是先分发到RenderObject中,且RenderObject的层级越深处理的优先级越高(参考hitTest方法)。RenderOnject再把具体事件注册到到手势识别器中接着参与手势竞技,GestureBinding会让还在竞技场中的第一个手势识别器获得胜利。

以上是关于浅谈Flutter核心机制之--- 事件分发的主要内容,如果未能解决你的问题,请参考以下文章

Android 进阶——Framework 核心之Touch事件分发机制详细攻略

Android 进阶——Framework 核心之Touch事件分发机制详细攻略

浅谈Android 事件分发机制

Android事件分发机制浅谈

以一个需求为例浅谈对事件分发机制的理解

原理一文深入了解Flutter事件机制