前言
咱们经过前面的知识了解了Listener只能处理比如简略的按下(PointerDownEvent
)、移动(PointerMoveEvent
)、抬起(PointerUpEvent
)、撤销(PointerCancelEvent
)等,简略的基本手势;而像长按、缩放、水平移动,还有手势之间的冲突处理等等都需求GestureRecogninzer处理;
RawGestureDetector内部是一个Listener组件,在触发了Listener.handleEvent
回调给RawGestureDetector._handlePointerDown
,_recognizers是一个Map,key是手识辨认器类型,而recognizer.addPointer
办法是进入GestureRecognizer的进口,这个当地触发机遇是在检测到按下时(PointerDownEvent
)
Map<Type, GestureRecognizer>? _recognizers = const <Type, GestureRecognizer>{};
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
for (final GestureRecognizer recognizer in _recognizers!.values)
recognizer.addPointer(event);
}
接下来从单一的单击手势(TapGestureRecognizer)和单一的长按手势(LongPressGestureRecognizer)以及两者简略的手势竞技开端了解手势辨认器的处理机制;
TapGestureRecognizer
手指按下PointerDownEvent
在手指按下时,单击手势辨认器的首要做两件事
- 将本身的
handleEvent
注册到触点路由中(GestureBinding.pointerRouter
) - 创立竞技场并参加竞技场
- 封闭竞技场开端竞技
isPointerAllowed 校验触点
该办法仅仅对触点类型的校验,规则是:_supportedDevices
(该特点可从外部传入)为空、或许包含该触点类型;校验经过则进行下一步
@protected
bool isPointerAllowed(PointerDownEvent event) {
// Currently, it only checks for device kind. But in the future we could check
// for other things e.g. mouse button.
return _supportedDevices == null || _supportedDevices!.contains(event.kind);
}
addAllowedPointer 注册触点
OneSequenceGestureRecognizer.addAllowedPointer
GestureRecognizer只提供了一个模板办法,需求子类完成;TapGestureRecognizer也没有完成该办法,经过层层调用会先进入OneSequenceGestureRecognizer.addAllowedPointer
## OneSequenceGestureRecognizer ##
void addAllowedPointer(PointerDownEvent event) {
startTrackingPointer(event.pointer, event.transform);
}
@protected
void startTrackingPointer(int pointer, [Matrix4? transform]) {
GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);
_trackedPointers.add(pointer);
assert(!_entries.containsValue(pointer));
_entries[pointer] = _addPointerToArena(pointer);
}
GestureArenaEntry _addPointerToArena(int pointer) {
if (_team != null)
return _team!.add(pointer, this);
return GestureBinding.instance!.gestureArena.add(pointer, this);
}
在startTrackingPointer
中会首先将handleEvent添加到触点路由中,便利在GestureBinding.handleEvent
时调用;
然后会经过_addPointerToArena
办法创立竞技场而且添加进竞技场,将得到的GestureArenaEntry
保存到_entries中;
随后进入到PrimaryPointerGestureRecognizer.addAllowedPointer
PrimaryPointerGestureRecognizer.addAllowedPointer
@override
void addAllowedPointer(PointerDownEvent event) {
super.addAllowedPointer(event);
if (state == GestureRecognizerState.ready) {
_state = GestureRecognizerState.possible;
_primaryPointer = event.pointer;
_initialPosition = OffsetPair(local: event.localPosition, global: event.position);
if (deadline != null)
_timer = Timer(deadline!, () => didExceedDeadlineWithEvent(event));
}
}
该办法会记载一些触点行为,和单击手势有关的只要_primaryPointer,_timer则是用来给长按手势用的。_initialPosition则是用来拖拽手势使用。
单击手势的addAllowedPointer(注册触点)效果完毕;
此刻,回到GestureBinding.handleEvent
办法中
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);
}
}
首先会经过pointerRouter.route(event);
调用单击手势辨认器的handleEvent
办法,TapGestureRecoginzer并没有完成该办法,经过调用最终会进入PrimaryPointerGestureRecognizer.handleEvent
PrimaryPointerGestureRecognizer.handleEvent
该办法会对触点停止进行校验,这儿的校验规则是假如按下后移动距离超越preAcceptSlopTolerance(在这儿是18逻辑像素),就会宣告当时单击手势竞技失利,随后调用BaseTapGestureRecognizer.rejectGesture
办法回调onTapCancel
办法,还有很重要的一步stopTrackingPointer
,该办法会移除触点路由pointerRouter的对应的handleEvent
@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)) {
resolve(GestureDisposition.rejected);
stopTrackingPointer(primaryPointer!);
} else {
handlePrimaryPointer(event);
}
}
stopTrackingIfPointerNoLongerDown(event);
}
假如触点校验成功,会进入到BaseTapGestureRecognizer.handlePrimaryPointer
办法中,
BaseTapGestureRecognizer.handlePrimaryPointer
## BaseTapGestureRecognizer ##
@override
void handlePrimaryPointer(PointerEvent event) {
if (event is PointerUpEvent) {
_up = event;
_checkUp();
} else if (event is PointerCancelEvent) {
resolve(GestureDisposition.rejected);
if (_sentTapDown) {
_checkCancel(event, '');
}
_reset();
} else if (event.buttons != _down!.buttons) {
resolve(GestureDisposition.rejected);
stopTrackingPointer(primaryPointer!);
}
}
能够看到并没有处理按下事情(PointerDownEvent)的办法,所以退出,
开端竞技
回到GestureBinding.handleEvent
里,接下来会履行gestureArena.close(event.pointer);
关于手势竞技场,咱们知道封闭竞技场意味着不能再有成员进入,而且开端竞技,而当时手势只要单击手势,所以会直接宣告单击手势取胜(关于竞技场的规则这儿不再赘述,不了解能够看这儿),在单击手势取胜后会进入BaseTapGestureRecognizer.acceptGesture
@override
void acceptGesture(int pointer) {
super.acceptGesture(pointer);
if (pointer == primaryPointer) {
_checkDown();
_wonArenaForPrimaryPointer = true;
_checkUp();
}
}
这个办法很简略,调用_checkDown()
回调onTapDown
,将_wonArenaForPrimaryPointer标记为true,这儿要留意一点,在按下手指不抬起时,虽然调用了_checkUp()
,可是该办法有约束,会判别_wonArenaForPrimaryPointer是否为ture
void _checkUp() {
if (!_wonArenaForPrimaryPointer || _up == null) {
return;
}
assert(_up!.pointer == _down!.pointer);
handleTapUp(down: _down!, up: _up!);
_reset();
}
此刻按下手势完毕,开端抬起手势(PointerUpEvent);
手指抬起PointerUpEvent
hanleEvent
在手指按下时,现已将该触点的handleEvent
添加到触点路由,所以此刻会直接触发手势辨认器的hanleEvent
办法,这儿会进入BaseTapGestureRecognizer.handlePrimaryPointer
## BaseTapGestureRecognizer ##
@override
void handlePrimaryPointer(PointerEvent event) {
if (event is PointerUpEvent) {
_up = event;
_checkUp();
}
...
}
很明显了,会直接履行_checkUp()
,由于咱们之前在手指按下时现已将_wonArenaForPrimaryPointer
标记为true,所以会履行handleTapUp
进行onTapUp
和onTap
事情回调,随后调用_reset()
重置
void _checkUp() {
if (!_wonArenaForPrimaryPointer || _up == null) {
return;
}
assert(_up!.pointer == _down!.pointer);
handleTapUp(down: _down!, up: _up!);
_reset();
}
void _reset() {
_sentTapDown = false;
_wonArenaForPrimaryPointer = false;
_up = null;
_down = null;
}
清扫竞技场
上面履行完后,再回到GestureBinding.handleEvent
中,最终是对竞技场的清扫,gestureArena.sweep(event.pointer);
@override // from HitTestTar。get
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);
}
}
这样单一单击手势的流程就完毕了。
LongPressGestureRecognizer
手指按下PointerDownEvent
addAllowedPointer 注册触点
长按手势和单击手势都是承继自PrimaryPointerGestureRecognizer,也便是说都会在addAllowedPointer
办法里将handEvent
注册给pointerRouter,而且创立和参加竞技场;
不同的是长按手势在手指按下时,会发动一个定时器,deadline外部可传入,默认是500ms,也便是说,按下时间超越500ms就会触发定时器的办法didExceedDeadlineWithEvent
,最终会履行到didExceedDeadline
,计时器的触发后的处理下面再说。这儿的首要效果和单击事情一样注册触点;
## PrimaryPointerGestureRecognizer ##
@override
void addAllowedPointer(PointerDownEvent event) {
super.addAllowedPointer(event);
if (state == GestureRecognizerState.ready) {
_state = GestureRecognizerState.possible;
_primaryPointer = event.pointer;
_initialPosition = OffsetPair(local: event.localPosition, global: event.position);
if (deadline != null)
_timer = Timer(deadline!, () => didExceedDeadlineWithEvent(event));
}
}
PrimaryPointerGestureRecognizer承继自OneSequenceGestureRecognizer,所以super.addAllowedPointer(event);
会调用OneSequenceGestureRecognizer的办法进行注册;
我在Flutter2.0.6版别上发现OneSequenceGestureRecognizer并没有addAllowedPointer这个办法,而在2.10.3的版别上OneSequenceGestureRecognizer新增了addAllowedPointer这个办法,首要效果便是盯梢注册路由到pointerRouter,本文的版别是2.10.3,这儿要留意一下;
这样该手势的handleEvent
也会注册到GestureBinding.pointerRouter
,这儿就完毕了,
handleEvent
随后履行GestureBinding.handleEvent
,进行手势辨认器的hanleEvent调用;
这儿要留意,当时GestureBinding.handleEvent
的调用机遇是在手指按下那一瞬间,而非等待长按手势完毕,所以在手指按下时就会调用GestureBinding.handleEvent
,在这儿进行handleEvent;
在手指按下时在GestureBinding.handleEvent
办法中履行长按手势辨认器的handleEvent
办法,前面说了由于单击和长按都是承继自PrimaryPointerGestureRecognizer,所以进行18逻辑像素校验后进入LongPressGestureRecognizer.handlePrimaryPointer
## LongPressGestureRecognizer ##
@override
void handlePrimaryPointer(PointerEvent event) {
...
} else if (event is PointerDownEvent) {
// The first touch.
_longPressOrigin = OffsetPair.fromEventPosition(event);
_initialButtons = event.buttons;
_checkLongPressDown(event);
} else if (event is PointerMoveEvent) {
if (event.buttons != _initialButtons) {
resolve(GestureDisposition.rejected);
stopTrackingPointer(primaryPointer!);
} else if (_longPressAccepted) {
_checkLongPressMoveUpdate(event);
}
}
}
为了便利检查,省略一些无关按下事情的代码
当时事情是PointerDownEvent,会用_longPressOrigin记载当时的坐标,用_initialButtons记载当时接触设备类型,然后调用_checkLongPressDown()
进行onLongPressDown
回调,留意这儿会把_longPressOrigin记载的坐标回调回去。
别的长按手势是能够移动的,从代码咱们能够看出,触发条件是长按手势取胜后,也便是_longPressAccepted=true
,会调用_checkLongPressMoveUpdate(event)
进行onLongPressMoveUpdate
回调,它与拖拽手势(DragGestureRecognizer)的触发差异便是长按手势是否取胜;
然后在GestureBinding.handleEvent
中封闭竞技场进行竞技,
gestureArena.close(event.pointer)
这儿特别阐明一下,此刻的长按手势并未被判决,咱们知道竞技场的close
办法会调用_tryToResolveArena
测验判决,由于现在竞技场只要一个长按手势成员,所以会调用_resolveByDefault
来宣告长按手势取胜,而且回调到LongPressGestureRecognizer.acceptGesture
中,可是这个办法是空完成,所以长按手势并不会在此判决,手指按下的事情完毕!
留意:竞技场判决成功后,竞技场管理者会将该竞技场移除,所以这儿的竞技场也没了。(这儿说的只要一个竞技成员时)
@override
void acceptGesture(int pointer) {
// Winning the arena isn't important here since it may happen from a sweep.
// Explicitly exceeding the deadline puts the gesture in accepted state.
}
定时器事情被触发
在注册触点时会发动一个500ms的定时器,该定时器的效果首要是用于检测长按手势的触发时长,也便是说有必要长按500ms才能触发该手势,定时器触发后,,该办法在PrimaryPointerGestureRecognizer只要一个断言,是在LongPressGestureRecognizer完成的详细逻辑
## LongPressGestureRecognizer ##
@override
void didExceedDeadline() {
// Exceeding the deadline puts the gesture in the accepted state.
resolve(GestureDisposition.accepted);
_longPressAccepted = true;
super.acceptGesture(primaryPointer!);
_checkLongPressStart();
}
能够看到在resolve(GestureDisposition.accepted);
中,会宣告该手势竞技取胜,由于之前在close
时将竞技场移除了,所以这儿不用关心这个了;
持续回到didExceedDeadline
履行,会将_longPressAccepted置为true,然后调用父类的PrimaryPointerGestureRecognizer.acceptGesture
办法
## PrimaryPointerGestureRecognizer ##
@override
void acceptGesture(int pointer) {
if (pointer == primaryPointer) {
_stopTimer();
_gestureAccepted = true;
}
}
在这儿会停止计时器,而且将_gestureAccepted
特点置为true(这个仅仅在触点位移校验用);
最终在LongPressGestureRecognizer.didExceedDeadline
中履行_checkLongPressStart()
办法,该办法首要回调onLongPressStart
和onLongPress
;
定时器的事情也完毕,这时分应该抬起手指了!
手指抬起PointerUpEvent
由于在手指按下和定时器触发时现已将所有使命完成了,手指抬起剩余的工作就仅仅回调长按手势的一些办法;
和单击手势一样,手指抬起时再次履行PrimaryPointerGestureRecognizer.handleEvent
,
最终调用到LongPressGestureRecognizer.handlePrimaryPointer
@override
void handlePrimaryPointer(PointerEvent event) {
...
if (event is PointerUpEvent) {
if (_longPressAccepted == true) {
_checkLongPressEnd(event);
} else {
// Pointer is lifted before timeout.
resolve(GestureDisposition.rejected);
}
_reset();
}
...
}
假如之前长按手势竞技取胜了(_longPressAccepted=true),履行_checkLongPressEnd
办法,回调onLongPressEnd
和onLongPressUp
,不然(_longPressAccepted=true)宣告竞技失利;
这儿回忆一下_longPressAccepted=true
的机遇,便是定时器被触发时(didExceedDeadline
),这是唯一一处设置的当地;
当然最终仍是回到GestureBinding.handleEvent
清扫竞技场,由于咱们之前在按下事情封闭竞技场时分(close
),将该竞技场移除了,所以这儿并不会做任何事。
gestureArena.sweep(event.pointer);
单一的长按手势就完毕了!