前言

咱们经过前面的知识了解了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进行onTapUponTap事情回调,随后调用_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()办法,该办法首要回调onLongPressStartonLongPress

定时器的事情也完毕,这时分应该抬起手指了!

手指抬起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办法,回调onLongPressEndonLongPressUp,不然(_longPressAccepted=true)宣告竞技失利;

这儿回忆一下_longPressAccepted=true的机遇,便是定时器被触发时(didExceedDeadline),这是唯一一处设置的当地;

当然最终仍是回到GestureBinding.handleEvent清扫竞技场,由于咱们之前在按下事情封闭竞技场时分(close),将该竞技场移除了,所以这儿并不会做任何事。

gestureArena.sweep(event.pointer);

单一的长按手势就完毕了!