手势竞技场是用来解决多个手势歧义的;
竞技相关的类如下⬇️
_GestrueArena
竞技场的实体类,该类有五个成员变量,一个办法
成员变量
-
isOpen
:表明该竞技场是否开放
-
isHeld
:表明该竞技场是否挂起
-
hasPendingSweep
:表明该竞技场是否等候清扫
-
eagerWinner
:巴望成功的参赛者 -
members
:竞技场的成员(参赛者)
办法
add(GestureArenaMember member)
;
添加成员到竞技场
void add(GestureArenaMember member) {
assert(isOpen); //添加时,竞技场一定是翻开的
members.add(member);
}
GestureArenaEntry
竞技场信息发送器,该类是私有类,只能在这个文件里被结构
成员变量
-
_arena
:竞技场办理者 -
_member
:竞技场成员 -
pointer
:触点id
办法
resolve(GestureDisposition disposition)
;
向竞技场办理者发送信息(成功或许失利)的接口类,这个类的在GestureArenaManager.add
中创立的
void resolve(GestureDisposition disposition) {
_arena._resolve(_pointer, _member, disposition);
}
GestureDisposition是一个枚举类型⬇️,别离表明成功、失利;
GestureArenaMember
这个类是一个笼统类,有两个笼统办法,别离表明成功回调和失利回调;
GestureRecognizer承继自该类,完结这两个办法以接收竞技成果。
GestureArenaManager
竞技场的办理者,办理多个竞技场,在整个Flutter生命周期内只有一个实例,保存在GestureBinding类中
成员变量
_arenas
;
final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
key是触点id,value是竞技场,也就说,它会办理多个竞技场。
办法
竞技场的保护办法
add
/// Adds a new member (e.g., gesture recognizer) to the arena.
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);
}
这个办法的首要作用是创立竞技场(假如不存在),添加参赛者到竞技场,回来一个GestureArenaEntry目标;
关于手势辨认器(GestureRecognizer),咱们知道手指按下会进行触点注册(addAllowedPointer
),其间会调用_addPointerToArena
办法,在这个办法里便是调用的它的add办法
## GestureRecognizer ##
GestureArenaEntry _addPointerToArena(int pointer) {
if (_team != null)
return _team!.add(pointer, this);
return GestureBinding.instance!.gestureArena.add(pointer, this);
}
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);
}
封闭竞技场,阻止新成员入内,调用_tryToResolveArena
测验开端竞技,留意:这个办法并不一定能分出输赢;
这个办法的调用是在GestureBinding.handleEvent
## GestureBinding ##
@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent) {
gestureArena.close(event.pointer);
}
...
}
hold
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));
}
避免竞技场被清扫,这个办法的完结很简单,仅仅将竞技场的isHeld
置为true;
这个办法的调用机遇是多组合手势,双击手势(搜遍源码,如同也只有双击手势才会挂起),它是第一次点击时分不出输赢的,还需求第2次点击,所以它在第一次点击抬起进行_registerFirstTap
时,会将竞技场挂起,避免被清扫,待第2次点击才进行判决,宣告自己成功。
## DoubleTapGestureRecognizer ##
void _registerFirstTap(_TapTracker tracker) {
_startDoubleTapTimer();
GestureBinding.instance!.gestureArena.hold(tracker.pointer);
// Note, order is important below in order for the clear -> reject logic to
// work properly.
_freezeTracker(tracker);
_trackers.remove(tracker.pointer);
_clearTrackers();
_firstTap = tracker;
}
void _registerSecondTap(_TapTracker tracker) {
_firstTap!.entry.resolve(GestureDisposition.accepted);
tracker.entry.resolve(GestureDisposition.accepted);
_freezeTracker(tracker);
_trackers.remove(tracker.pointer);
_checkUp(tracker.initialButtons);
_reset();
}
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操作发生在手指抬起时(PointerUpEvent),也便是在GestureBinding.handleEvent
,保证处理假如竞技场成员间没有取胜者而陷入的僵局。能够了解为最终的判决。
而假如之前现已判决成功了,这儿的state就会为空(由于判决成功会从_arenas中移除竞技场);
这儿还有一个条件,假如竞技场是挂起状况(state.isHeld
),则会讲竞技场hasPendingSweep
(待清扫)标记为true,而且退出;咱们从这句注释中也能看出,这个long-lived member便是咱们上面说到的双击手势。
This arena is being held for a long-lived member.
所以,在这儿也能了解hold的作用了;
## 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);
}
...
}
release
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);
}
已然竞技场被挂起时(isHeld
),是无法被清扫(sweep)的,所以需求一个操作让竞技场取消挂起以便清扫,这儿用到的便是release;
这个办法做了两件事,首先将isHeld
标记为false;然后假如竞技场是待清扫状况(hasPendingSweep
)则直接进行清扫;
这个办法的调用场景是在DoubleTapGestureRecognizer._reset
中被调用的,而且咱们也看到只对第一次的tap履行release,第2次不会(由于第2次没被挂起)
## DoubleTapGestureRecognizer ##
void _reset() {
_stopDoubleTapTimer();
if (_firstTap != null) {
if (_trackers.isNotEmpty)
_checkCancel();
// Note, order is important below in order for the resolve -> reject logic
// to work properly.
final _TapTracker tracker = _firstTap!;
_firstTap = null;
_reject(tracker);
GestureBinding.instance!.gestureArena.release(tracker.pointer);
}
_clearTrackers();
}
竞技场的判决办法
_resolveInFavorOf
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);
for (final GestureArenaMember rejectedMember in state.members) {
if (rejectedMember != member)
rejectedMember.rejectGesture(pointer);
}
member.acceptGesture(pointer);
}
看这个办法名也能看出来,这个办法是使成员member直接取胜的办法,其他成员失利,是一定会判决出成果的;
该办法的的触发机遇有两处:
-
_resolve
:假如disposition == GestureDisposition.accepted
,而且竞技场状况是封闭时,会直接调用_resolveInFavorOf
进行判决; -
_tryToResolveArena
:假如当时竞技场有巴望成功的参赛者(eagerWinner
),会直接调用_resolveInFavorOf
进行判决;
_resolveByDefault
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);
}
这个办法会使唯一的成员取胜;是一定会判决出成果的。
该办法的唯一触发机遇是在_tryToResolveArena
中,当成员只有一个时才会调用的;
_tryToResolveArena
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) {
assert(_debugLogDiagnostic(pointer, 'Eager winner: ${state.eagerWinner}'));
_resolveInFavorOf(pointer, state, state.eagerWinner!);
}
}
测验判决,听姓名咱们就知道,这个是不一定能判决出成果。
代码完结对应三种状况:
- 竞技场成员只有一个:调用
_resolveByDefault
宣告该成员取胜,能判决出成果 - 竞技场是空的:直接从_arenas移除该竞技场
- 竞技场有巴望成功者,调用
_resolveInFavorOf
宣告该成员取胜,能判决出成果 不然,直接回来,不会有判决成果。
该办法的调用机遇有两处:
-
close
:在竞技场封闭时,会调用该办法测验判决 -
_resolve
:假如disposition == GestureDisposition.rejected
,而且竞技场是封闭状况会调用_tryToResolveArena
测验判决。
_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) {
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);
}
}
}
关于这个办法,在GestureArenaEntry中说到过,GestureArenaEntry.resolve
最终会调用到这儿;
这个办法是十分霸道的,直接经过disposition
参数操控判决成果;当入参disposition == GestureDisposition.accepted时,是一定会分出判决成果的,而假如是失利则不一定能判决出成果;
再回头看这个办法的完结,
假如决策参数是失利(GestureDisposition.rejected
),
- 从该竞技场移除该成员
- 向该成员发送失利的回调
- 假如该竞技场目前是封闭状况,则调用
_tryToResolveArena
测验判决
假如决策参数是成功(GestureDisposition.accepted
)
- 假如竞技场是翻开状况,设置该成员为巴望成功者(
eagerWinner
) - 假如竞技场是封闭,调用
_resolveInFavorOf
办法宣告该成员成功
从手势辨认器了解
这么多办法会很乱,很难了解,咱们从手势的运行周期捋一下: 假如当时咱们有单击事情(TapGestureRecognizer)和长按事情(LongPressGestureRecognizer)
当手指按下(PointerDownEvent):
-
TapGestureRecognizer和LongPressGestureRecognizer会别离进行触点注册(
addAllowedPointer
),在触点注册时会创立竞技场、GestureArenaEntry目标,并把自己加入竞技场,这样竞技场内成员就有他们两个了;LongPressGestureRecognizer会在注册时开启一个500ms的定时器; -
此刻履行
GestureBinding.handleEvent
办法,pointerRouter.route(event)
这儿会调用手势辨认器的handleEvent
,可是在这儿并没有判决的办法,所以咱们忽略退出; -
随后履行
gestureArena.close(event.pointer);
,前面咱们讲过在这儿会封闭竞技场调用_tryToResolveArena
办法测验判决,可是看条件,这儿并不会判决出成果,所以也会退出。这样按下事情的GestureBinding.handleEvent
履行完了。 -
此刻长按手势的500ms定时器事情触发,然后调用
GestureArenaEntry.resolve
宣告成功,
@override
void didExceedDeadline() {
// Exceeding the deadline puts the gesture in the accepted state.
resolve(GestureDisposition.accepted);
_longPressAccepted = true;
super.acceptGesture(primaryPointer!);
_checkLongPressStart();
}
当手指抬起(PointerUpEvent)
由于之前现已判决完结,咱们知道判决完结会从GestureArenaManager._arenas
移除该竞技场,一切抬起手指后的gestureArena.sweep(event.pointer);
并不会做任何事
总结
GestureArenaManager中的五个保护办法中,其间的close、sweep会参与判决(release也会,可是它是间接调用了sweep);
而这两个办法是只被在GestureBinding.handleEvent
中的调用的:
## 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);
}
}
gestureArena.close(event.pointer);
会测验判决,可是不一定能分出输赢;
gestureArena.sweep(event.pointer);
清扫竞技场,是作为竞技流程的最终一道保障,避免呈现无法分出输赢的可能。
而在手势辨认器内部,他们能够调用GestureArenaEntry.resolve
在合适的机遇直接宣告成功或许失利。
还要留意一点的是,一旦某个成员被判决成功,该竞技场会宣告其他成员失利,而且从GestureArenaManager._arenas
中移除该竞技场;而假如仅仅宣告该成员失利,在没有成员取胜的状况并不会移除该竞技场,仅仅从该竞技场中移除该成员,在GestureArenaEntry.resolve
能看到相关完结。