Flutter 手势事件处理机制

参考资料Flutter实战

GestureDetector:

  • GestureDetector: 手势的识别和处理都是在 Flutter 事件分发阶段发生的
  • 手势的处理是有一个竞技场的概念,每一个 GestureRecognizer 都是竞技场的成员,最终会在竞技场中决出一个 GestureRecognizer 来处理手势事件
  • 下面以 TapGestureRecognizer 为例去分析,分析前先看一下它的类继承的结构:

TapGestureRecognizer -> BaseTapGestureRecognizer -> PrimaryPointerGestureRecognizer -> OneSequenceGestureRecognizer -> GestureRecognizer -> GestureArenaMember

从两个阶段来分析 Tap 手势的处理过程

手势识别器添加到竞技场阶段

GestureDetector build 方法

  1. 构建了手势识别器的 Map<Type, GestureRecognizerFactory> 集合,将 Tap,Scale 等手势识别器添加到集合中
  2. 将构建的手势识别器的集合传给 RawGestureDetector,并返回 RawGestureDetector 实例对象
  3. RawGestureDetectorState build 方法中又构建了 Listener
  4. RawGestureDetectorState 的 _handlePointerDown() 方法传给 Listener 的 onPointerDown 属性

RawGestureDetectorState _handlePointerDown() 方法流程解析:

1.首先遍历手势识别器集合,然后调用手势识别器的 GestureRecognizer addPointer 方法,将 PointerDownEvent 事件,添加进去,看下 addPointer 方法:

   void addPointer(PointerDownEvent event) {
     _pointerToKind[event.pointer] = event.kind;
     // isPointerAllowed 方法在每个手势识别器中都有实现,作用是检查该识别器是否允许跟踪指针。
     if (isPointerAllowed(event)) {
        // 将当前的手势识别器和事件注册到 PointerRouter 中,此方法在每个手势识别器中都有实现
       addAllowedPointer(event);
     } else {
       handleNonAllowedPointer(event);
     }
   }
复制代码

addAllowedPointer 方法:

TapGestureRecognizer 为例,它并没有实现这个方法,但是它的父类有实现,这里重点看一下 OneSequenceGestureRecognizer 类:

  @override
  @protected
  void addAllowedPointer(PointerDownEvent event) {
    startTrackingPointer(event.pointer, event.transform);
  }
  
  @protected
  void startTrackingPointer(int pointer, [Matrix4? transform]) {
  // 1、关于 PointerRouter 对象,GestureBinding 是在 runApp 执行时创建的,对于整个应用程序来说就是全局的
  // 2、重点说一下,handleEvent 参数是 OneSequenceGestureRecognizer 的方法(回调方法),在其子类
  // PrimaryPointerGestureRecognizer 中有具体的实现.
    GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);
    _trackedPointers.add(pointer);
    assert(!_entries.containsValue(pointer));
    // 将 pointer 添加到手势竞技场
    _entries[pointer] = _addPointerToArena(pointer);
  }
  
  GestureArenaEntry _addPointerToArena(int pointer) {
    if (_team != null)
      return _team!.add(pointer, this);
      // 添加到竞技场,并且将当前的 Recognizer 对象添加进去。
      // 注意: GestureRecognizer 是继承 GestureArenaMember 的,也就是说手势识别器都是手势竞技场的成员
    return GestureBinding.instance!.gestureArena.add(pointer, this);
  }
复制代码

到此整个手势事件的添加分析完成,可以总结为下面的流程图

A290927B-B514-4998-A47C-51F5BD0015BA.png

手势分发处理阶段

在 runApp 方法中,会初始化 WidgetsFlutterBinding,而 WidgetsFlutterBinding 继承自 RendererBinding 继承自 GestureBinding。接下来看它们的 hitTest 方法(Flutter 事件分发机制)

  // RendererBinding hitTest
   @override
   void hitTest(HitTestResult result, Offset position) {
     assert(renderView != null);
     assert(result != null);
     assert(position != null);
     renderView.hitTest(result, position: position);
     super.hitTest(result, position);
   }
   
  // GestureBinding hitTest
   @override // from HitTestable
   void hitTest(HitTestResult result, Offset position) {
     result.add(HitTestEntry(this));
   }
复制代码

总结:

  • 从上面的代码可以看到,最终 GestureBinding 也被添加到 HitTestResult 中了,那么在 Flutter 事件分发中 GestureBinding 的 handleEvent() 方法也会被调用。且在 Flutter 事件分发的过程中 GestureBinding dispatchEvent()方法 与 handleEvent() 方法中都调用了pointerRouter.route(event); 而 pointerRouter.route(event) 方法会调用在 pointerRouter 中添加的 GestureRecognizer 的 handleEvent() 方法。

接下来主要看下 GestureBinding 的 handleEvent 方法

GestureBinding handleEvent 方法:

  @override // from HitTestTarget
  void handleEvent(PointerEvent event, HitTestEntry entry) {
  // 1、会调用之前添加的 GestureRecognizer 的 handleEvent() 方法
    pointerRouter.route(event);
    if (event is PointerDownEvent) {
    // 2、关闭手势竞技场,并尝试解决手势竞技场
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent) {
    // 3、扫描手势竞技场,并调用第一个手势识别器的 acceptGesture 方法
      gestureArena.sweep(event.pointer);
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
  }
复制代码

对上面注释 1 的地方分析

这里还是继续以 TapGestureRecognizer 来分析,handlerEvent 是在它的父类 PrimaryPointerGestureRecognizer 中实现的,源码如下:

@override
  void handleEvent(PointerEvent event) {
    assert(state != GestureRecognizerState.ready);
    if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
      final bool isPreAcceptSlopPastTolerance =
          !_gestureAccepted &&
          preAcceptSlopTolerance != null &&
          _getGlobalDistance(event) > preAcceptSlopTolerance!;
      final bool isPostAcceptSlopPastTolerance =
          _gestureAccepted &&
          postAcceptSlopTolerance != null &&
          _getGlobalDistance(event) > postAcceptSlopTolerance!;

      if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
        // 最终会调用到 GestureArenaManager 中的 _resolve
        resolve(GestureDisposition.rejected);
        stopTrackingPointer(primaryPointer!);
      } else {
        handlePrimaryPointer(event);
      }
    }
    stopTrackingIfPointerNoLongerDown(event);
  }
复制代码

总结:

  • 首先调用时机,它会在手势事件分发时进行调用
  • 其具体作用:会对当前事件进行手势识别,并决定当前的 Recognizer 是 GestureDisposition.rejected 还是 GestureDisposition.accepted,如果是 rejected 就会从一直调用到 GestureArenaManager._resolve() 方法,此方法会在下面分析
  • 对于拖动的手势识别器,它还会回调 update 方法,具体可以查看:DragGestureRecognizer 识别器

GestureArenaManager _resolve 方法

 void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
   final _GestureArena? state = _arenas[pointer];
   if (state == null)
     return; // This arena has already resolved.
   assert(_debugLogDiagnostic(pointer, '${ disposition == GestureDisposition.accepted ? "Accepting" : "Rejecting" }: $member'));
   assert(state.members.contains(member));
   if (disposition == GestureDisposition.rejected) {
       // 如果是拒绝的就会从 members 中移除,那么之后这个手势识别器就不会在处理事件了
     state.members.remove(member);
     member.rejectGesture(pointer);
     if (!state.isOpen)
       _tryToResolveArena(pointer, state);
   } else {
     assert(disposition == GestureDisposition.accepted);
     if (state.isOpen) {
       state.eagerWinner ??= member;
     } else {
       assert(_debugLogDiagnostic(pointer, 'Self-declared winner: $member'));
       _resolveInFavorOf(pointer, state, member);
     }
   }
 }
复制代码

接下来分析注释 2 GestureArenaManager 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);
    }
   
   // 尝试解决手势竞技场
   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) { 
        // eagerWinner 注解:(有道翻译)如果一个成员试图在竞技场还开放的时候获胜,他就会成为“渴望的赢家”。当竞技场对新参与者关闭时,我们会寻找一个渴望成功的人,如果有的话,我们会在那个时候解决这个竞技场。
        assert(_debugLogDiagnostic(pointer, 'Eager winner: ${state.eagerWinner}'));
        _resolveInFavorOf(pointer, state, state.eagerWinner!);
      } 
   }
   
  // 解决手势竞技场的方法,逻辑是取手势成员中的第一个,调用它的 acceptGesture 方法
  void _resolveByDefault(int pointer, _GestureArena state) {
     if (!_arenas.containsKey(pointer))
       return; // Already resolved earlier.
     assert(_arenas[pointer] == state);
     assert(!state.isOpen);
     final List<GestureArenaMember> members = state.members;
     assert(members.length == 1);
     _arenas.remove(pointer);
     assert(_debugLogDiagnostic(pointer, 'Default winner: ${state.members.first}'));
     state.members.first.acceptGesture(pointer);
  }
  
  // 处理手势识别器成员是 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);
     // 遍历竞技场中的所有成员,并调用它们的 rejectGesture 
     for (final GestureArenaMember rejectedMember in state.members) {
       if (rejectedMember != member)
         rejectedMember.rejectGesture(pointer);
     }
     // 只有 eagerWinner 才会执行 acceptGesture 
     member.acceptGesture(pointer);
  }
    
复制代码

总结:

  • 从上面源码可以看出,在关闭手势竞技场时,会尝试解决这个手势竞技场,如果竞技场只有一个成员,那么就直接使用这个手势识别器的来处理这个事件,如果有多个成员的话会通过 sweep 方来解决

接下来分析注释 3 GestureArenaManager gestureArena.sweep 方法

    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) {
          // First member wins.
          assert(_debugLogDiagnostic(pointer, 'Winner: ${state.members.first}'));
          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);
        }
    }
复制代码

总结:

  • sweep 方法逻辑很简单,直接取竞技场中的第一个成员来接收处理手势事件。

接下来看下 GestureArenaMember 中的 acceptGesture 和 rejectGesture

GestureArenaMember acceptGesture 方法

  • 对于此方法每个手势识别器的处理是不一样的,看一下 BaseTapGestureRecognizer 中的源码:
  @override
  void acceptGesture(int pointer) {
    super.acceptGesture(pointer);
    if (pointer == primaryPointer) {
      _checkDown();
      _wonArenaForPrimaryPointer = true;
      _checkUp();
    }
  }
复制代码

可以看到 Tap 中此方法作用,是处理了 down 和 up 事件,接下来看它的父类

  • BaseTapGestureRecognizer 的父类 PrimaryPointerGestureRecognizer
@override
  void acceptGesture(int pointer) {
    if (pointer == primaryPointer) {
       //停止定时器,如果定时器非空,识别器将在开始跟踪主指针的时间过了一段时间后调用[didExceedDeadline]。
       //在Tap中 时间是 100 ms 
      _stopTimer();
      _gestureAccepted = true;
    }
  }
复制代码
  • BaseTapGestureRecognizer 中实现了 didExceedDeadline 方法
  @override
  void didExceedDeadline() {
    //当定时器时间到的时候,会执行处理 down 事件
    _checkDown();
  }
复制代码

GestureArenaMember rejectGesture 方法

  • BaseTapGestureRecognizer 的源码:
 @override
  void rejectGesture(int pointer) {
    super.rejectGesture(pointer);
    if (pointer == primaryPointer) {
      // Another gesture won the arena.
      assert(state != GestureRecognizerState.possible);
      if (_sentTapDown)
        // 执行手势事件取消的回调
        _checkCancel(null, 'forced');
        // 重置手势识别器的状态 
      _reset();
    }
  }
复制代码
  • PrimaryPointerGestureRecognizer 源码:
  @override
  void rejectGesture(int pointer) {
    if (pointer == primaryPointer && state == GestureRecognizerState.possible) {
       // 停止定时器 
      _stopTimer();
      // 手势识别器状态改为 失灵的不再使用的
      _state = GestureRecognizerState.defunct;
    }
  }
复制代码

手势分发处理流程图

6FF51C40-E4F1-481B-A3DB-D4F6FE648509.png

到此整个手势的处理流程就分析完毕了

根据上面的分析,可以对一些手势冲突的场景进行总结

  • Tap 手势是有 Deadline 的,也就是事件响应是有个定时时间限制的,具体的表现为:有两个重叠的 Tap 手势,如果长按超过 Deadline 的时间限制,那么它们的 down 事件都会执行,但是 up 事件只有最内层的会执行,因为 GestureArenaManager sweep 方法选择第一个 Recognizer 来处理手势事件。
  • 如果两个 Tap 手势是重叠的如果都想去响应 down 和 up 事件:
    • 那么可以重写 Recognizer 的 rejectGesture 方法并在内部强制调用 acceptGesture
    • 使用 Listener 来替换其中一个手势监听,就相当于跳出 Gesture 规则,且 Listener 会优先响应手势事件
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享