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
- Build the Map
,>
set of gesture recognizers, and add Tap, Scale and other gesture recognizers to the set
- Pass the built collection of gesture recognizers to RawGestureDetector and return the RawGestureDetector instance object
- The Listener is also built in the RawGestureDetectorState Build method
- 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