References Flutter actual combat

GestureDetector:

  • GestureDetector: Gesture recognition and processing occur during the Flutter event distribution phase
  • GestureRecognizer each GestureRecognizer is a member of the arena, and one GestureRecognizer will be chosen to handle gesture events in the arena
  • The following uses TapGestureRecognizer as an example to analyze, before analyzing its class inheritance structure:

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

Analyze the Tap gesture processing process from two stages

Gesture recognizer added to arena stage

GestureDetector the build method

  1. Build the Map

    set of gesture recognizers, and add Tap, Scale and other gesture recognizers to the set
    ,>
  2. Pass the built collection of gesture recognizers to RawGestureDetector and return the RawGestureDetector instance object
  3. The Listener is also built in the RawGestureDetectorState Build method
  4. RawGestureDetectorState’s _handlePointerDown() method passes to the Listener’s onPointerDown property

RawGestureDetectorState _handlePointerDown()

Add the PointerDownEvent event to the GestureRecognizer addPointer ();

   void addPointer(PointerDownEvent event) {
     _pointerToKind[event.pointer] = event.kind;
     The isPointerAllowed method is implemented in each gesture recognizer to check whether the recognizer allows tracking Pointers.
     if (isPointerAllowed(event)) {
        // Registers the current gesture recognizer and event to the PointerRouter, as implemented in each gesture recognizer
       addAllowedPointer(event);
     } else{ handleNonAllowedPointer(event); }}Copy the code

AddAllowedPointer method:

TapGestureRecognizer, for example, it does not implement this method, but it is the parent of the implementation, focus here see OneSequenceGestureRecognizer class:

  @override
  @protected
  void addAllowedPointer(PointerDownEvent event) {
    startTrackingPointer(event.pointer, event.transform);
  }
  
  @protected
  void startTrackingPointer(int pointer, [Matrix4? transform]) {
  For the PointerRouter object, the GestureBinding is created at runApp execution time and is global to the entire application
  / / 2, key, handleEvent parameter is OneSequenceGestureRecognizer method (callback methods), on its subclasses
  / / PrimaryPointerGestureRecognizer in a specific implementation.GestureBinding.instance! .pointerRouter.addRoute(pointer, handleEvent, transform); _trackedPointers.add(pointer);assert(! _entries.containsValue(pointer));// Add pointer to the gesture arena
    _entries[pointer] = _addPointerToArena(pointer);
  }
  
  GestureArenaEntry _addPointerToArena(int pointer) {
    if(_team ! =null)
      return_team! .add(pointer,this);
      // Add the current Recognizer object to the arena.
      // Note: GestureRecognizer descends from GestureArenaMember, which means that gesture recognizers are members of the gesture arena
    returnGestureBinding.instance! .gestureArena.add(pointer,this);
  }
Copy the code

By the time this whole gesture event is analyzed, it can be summarized as the following flow chart

Gesture distribution processing stage

In the runApp method, WidgetsFlutterBinding is initialized, and WidgetsFlutterBinding inherits from RendererBinding and inherits from GestureBinding. Now look at their hitTest method (Flutter event distribution mechanism)

  // 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));
   }
Copy the code

Conclusion:

  • As you can see from the code above, the GestureBinding is also added to HitTestResult. Then the GestureBinding handleEvent() method will be called in the Flutter event distribution. Pointerrouter.route (event) is called in both the GestureBinding dispatchEvent() method and the handleEvent() method. The PointerRouter.route (event) method calls the handleEvent() method of GestureRecognizer added to the pointerRouter.

Next we’ll focus on the handleEvent method of GestureBinding

GestureBinding handleEvent method:

  @override // from HitTestTarget
  void handleEvent(PointerEvent event, HitTestEntry entry) {
  // the handleEvent() method of GestureRecognizer is called
    pointerRouter.route(event);
    if (event is PointerDownEvent) {
    // 2, close gesture arena and try to resolve gesture arena
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent) {
    // Scan the gesture arena and call the acceptGesture method of the first gesture recognizer
      gestureArena.sweep(event.pointer);
    } else if (event isPointerSignalEvent) { pointerSignalResolver.resolve(event); }}Copy the code

Analyze note 1 above

Continued to analyze TapGestureRecognizer here, handlerEvent is implemented in its superclass PrimaryPointerGestureRecognizer, source code is as follows:

@override
  void handleEvent(PointerEvent event) {
    assert(state ! = GestureRecognizerState.ready);if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
      final boolisPreAcceptSlopPastTolerance = ! _gestureAccepted && preAcceptSlopTolerance ! =null&& _getGlobalDistance(event) > preAcceptSlopTolerance! ;final boolisPostAcceptSlopPastTolerance = _gestureAccepted && postAcceptSlopTolerance ! =null&& _getGlobalDistance(event) > postAcceptSlopTolerance! ;if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
        // _resolve in GestureArenaManager is eventually calledresolve(GestureDisposition.rejected); stopTrackingPointer(primaryPointer!) ; }else {
        handlePrimaryPointer(event);
      }
    }
    stopTrackingIfPointerNoLongerDown(event);
  }
Copy the code

Conclusion:

  • The first call is timing, which is called when the gesture event is dispatched
  • Its specific role: On current events will gesture recognition, and determine the current Recognizer is GestureDisposition rejected or GestureDisposition. Accepted, If it is rejected, it is called all the way to the gestureArenamanager._resolve () method, which is examined below
  • It also calls back to the update method for DragGestureRecognizer recognizer

GestureArenaManager _resolve method

 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) {
       // If it is rejected it is removed from members and the gesture recognizer is no longer processing events
     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); }}}Copy the code

2 GestureArenaManager Gesturearena. close

    // Close the gesture arena
    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));
       // Try to resolve the gesture arena
       _tryToResolveArena(pointer, state);
    }
   
   // Try to resolve the gesture arena
   void _tryToResolveArena(int pointer, _GestureArena state) {
       assert(_arenas[pointer] == state);
       assert(! state.isOpen);if (state.members.length == 1) {
        // If there is only one gesture member, use it directly to process the event and prioritize its event execution through the microtask queue
         scheduleMicrotask(() => _resolveByDefault(pointer, state));
       } else if (state.members.isEmpty) {
       // If empty, it is removed directly from the gesture arena
         _arenas.remove(pointer);
         assert(_debugLogDiagnostic(pointer, 'Arena empty.'));
      } else if(state.eagerWinner ! =null) { 
        If a member attempts to win while the arena is still open, he becomes the "eagerWinner". When the arena is closed to new entrants, we will look for someone who is hungry for success, if there is one, and we will address the arena at that time.
        assert(_debugLogDiagnostic(pointer, 'Eager winner: ${state.eagerWinner}'));
        _resolveInFavorOf(pointer, state, state.eagerWinner!);
      } 
   }
   
  The logic to resolve the gesture arena is to take the first gesture member and call its acceptGesture method
  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);
  }
  
  // Handling gesture recognizer members is eagerWinner's logic
  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);// Iterate over all members in the arena and invoke their rejectGesture
     for (final GestureArenaMember rejectedMember in state.members) {
       if(rejectedMember ! = member) rejectedMember.rejectGesture(pointer); }// Only eagerWinner will execute acceptGesture
     member.acceptGesture(pointer);
  }
    
Copy the code

Conclusion:

  • As you can see from the source code above, when the gesture arena is closed, an attempt is made to resolve the gesture arena. If the arena has only one member, the gesture recognizer is used to handle the event directly, and if there are multiple members, the gesture recognizer is used to resolve the event

Note 3 GestureArenaManager Gesturearena. sweep method

    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); }}Copy the code

Conclusion:

  • The logic of the sweep method is simple; it simply takes the first member of the arena to receive and process the gesture event.

Next take a look at acceptGesture and rejectGesture in GestureArenaMember

GestureArenaMember acceptGesture method

  • Each gesture recognizer handles this method differently. Take a look at the source BaseTapGestureRecognizer:
  @override
  void acceptGesture(int pointer) {
    super.acceptGesture(pointer);
    if (pointer == primaryPointer) {
      _checkDown();
      _wonArenaForPrimaryPointer = true; _checkUp(); }}Copy the code

As you can see, this method in Tap handles down and Up events, and then its parent class

  • BaseTapGestureRecognizer superclass PrimaryPointerGestureRecognizer:
@override
  void acceptGesture(int pointer) {
    if (pointer == primaryPointer) {
       // Stop the timer. If the timer is not empty, the recognizer will call [didExceedDeadline] after some time has passed since it started tracking the main pointer.
       // In Tap the time is 100 ms
      _stopTimer();
      _gestureAccepted = true; }}Copy the code
  • BaseTapGestureRecognizer implements the didExceedDeadline method
  @override
  void didExceedDeadline() {
    // When the timer time expires, the process down event is executed
    _checkDown();
  }
Copy the code

GestureArenaMember rejectGesture method

  • BaseTapGestureRecognizer BaseTapGestureRecognizer
 @override
  void rejectGesture(int pointer) {
    super.rejectGesture(pointer);
    if (pointer == primaryPointer) {
      // Another gesture won the arena.
      assert(state ! = GestureRecognizerState.possible);if (_sentTapDown)
        // Execute the callback to cancel the gesture event
        _checkCancel(null.'forced');
        // Reset the state of the gesture recognizer_reset(); }}Copy the code
  • PrimaryPointerGestureRecognizer source code:
  @override
  void rejectGesture(int pointer) {
    if (pointer == primaryPointer && state == GestureRecognizerState.possible) {
       // Stop the timer
      _stopTimer();
      // The gesture recognizer state changes to malfunctioning and no longer in use_state = GestureRecognizerState.defunct; }}Copy the code

Gesture distribution processing flowchart

To this whole gesture processing process analysis is finished

According to the above analysis, some scenes of gesture conflict can be summarized

  • The Tap gesture has a Deadline, that is, the event response has a time limit. There are two overlapping Tap gestures. If you press past the Deadline, both down events will be executed, but only the innermost up event will be executed. Because the GestureArenaManager sweep method chooses the first Recognizer to handle gesture events.
  • If two Tap gestures overlap if both want to respond to down and Up events:
    • Recognizer’s rejectGesture method can then be overwritten and internally enforced to call acceptGesture
    • Replacing one of the Gesture listeners with a Listener is like jumping out of the Gesture rule, and the Listener responds to the Gesture event first