GestureDetector:
- GestureDetector: 手势的识别和处理都是在 Flutter 事件分发阶段发生的
- 手势的处理是有一个竞技场的概念,每一个 GestureRecognizer 都是竞技场的成员,最终会在竞技场中决出一个 GestureRecognizer 来处理手势事件
- 下面以 TapGestureRecognizer 为例去分析,分析前先看一下它的类继承的结构:
TapGestureRecognizer -> BaseTapGestureRecognizer -> PrimaryPointerGestureRecognizer -> OneSequenceGestureRecognizer -> GestureRecognizer -> GestureArenaMember
从两个阶段来分析 Tap 手势的处理过程
手势识别器添加到竞技场阶段
GestureDetector build 方法
- 构建了手势识别器的 Map<Type, GestureRecognizerFactory> 集合,将 Tap,Scale 等手势识别器添加到集合中
- 将构建的手势识别器的集合传给 RawGestureDetector,并返回 RawGestureDetector 实例对象
- RawGestureDetectorState build 方法中又构建了 Listener
- 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);
}
复制代码
到此整个手势事件的添加分析完成,可以总结为下面的流程图
手势分发处理阶段
在 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;
}
}
复制代码
手势分发处理流程图
到此整个手势的处理流程就分析完毕了
根据上面的分析,可以对一些手势冲突的场景进行总结
- Tap 手势是有 Deadline 的,也就是事件响应是有个定时时间限制的,具体的表现为:有两个重叠的 Tap 手势,如果长按超过 Deadline 的时间限制,那么它们的 down 事件都会执行,但是 up 事件只有最内层的会执行,因为 GestureArenaManager sweep 方法选择第一个 Recognizer 来处理手势事件。
- 如果两个 Tap 手势是重叠的如果都想去响应 down 和 up 事件:
- 那么可以重写 Recognizer 的 rejectGesture 方法并在内部强制调用 acceptGesture
- 使用 Listener 来替换其中一个手势监听,就相当于跳出 Gesture 规则,且 Listener 会优先响应手势事件