Flutter事件傳遞與手勢(shì)識(shí)別

Flutter的事件源

Flutter的原始事件是由window中 PointerDataPacketCallback(PointerDataPacket packet) 回調(diào)獲得的翩蘸,這個(gè)回調(diào)再GestureBinding初始化中就設(shè)置了window.onPointerDataPacket = _handlePointerDataPacket沾凄,我們看一下_handlePointerDataPacket的代碼

void _handlePointerDataPacket(ui.PointerDataPacket packet) {
  //_pendingPointerEvents是一個(gè)PointerEvent的隊(duì)列浦箱,這段代碼的意思是將PointerDataPacket轉(zhuǎn)換成PointerEvent然后存在隊(duì)列中
  _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
  if (!locked)
    _flushPointerEventQueue();
}

//對(duì)隊(duì)列中的PointerEvent進(jìn)行出隊(duì)并依次處理
void _flushPointerEventQueue() {
  assert(!locked);
  while (_pendingPointerEvents.isNotEmpty)
    _handlePointerEvent(_pendingPointerEvents.removeFirst());//_handlePointerEvent對(duì)每一個(gè)PointerEvent進(jìn)行處理
}

_handlePointerEvent方法才是對(duì)每個(gè)PointerEvent進(jìn)行處理的地方

void _handlePointerEvent(PointerEvent event) {
  HitTestResult hitTestResult;
  if (event is PointerDownEvent) {
    assert(!_hitTests.containsKey(event.pointer));
    hitTestResult = HitTestResult();
    hitTest(hitTestResult, event.position);//hitTest方法,來(lái)確定命中測(cè)試的結(jié)果
    _hitTests[event.pointer] = hitTestResult;//event.pointer是每次連續(xù)的PointEvent的唯一id嘲玫,以id為key將hitTestResult存到_hitTests中
  } else if (event is PointerUpEvent || event is PointerCancelEvent) {
    hitTestResult = _hitTests.remove(event.pointer);//事件結(jié)束標(biāo)記,將hitTestResult從_hitTests取出并移除
  } else if (event.down) {
    hitTestResult = _hitTests[event.pointer];//move事件直接重用down事件的hitTestResult娇昙,避免每次都進(jìn)行命中測(cè)試
  }
  if (hitTestResult != null ||
      event is PointerHoverEvent ||
      event is PointerAddedEvent ||
      event is PointerRemovedEvent) {
    dispatchEvent(event, hitTestResult);//分發(fā)Event
  }
}

_handlePointerEvent中比較重要的兩點(diǎn):

  1. 在PointerDownEvent事件時(shí)谓晌,通過hitTest方法來(lái)計(jì)算出HitTestResult
  2. 對(duì)事件序列通過dispatchEvent(event, hitTestResult)進(jìn)行分發(fā)事件

上面的過程就是將原始事件轉(zhuǎn)換成我們需要的PointEvent,然后再確定命中測(cè)試結(jié)果蚕涤,最后再進(jìn)行分發(fā)事件筐赔。

確定HitTestResult

HitTestResult中有一個(gè)List<HitTestEntry> _path的字段,由多個(gè)HitTestEntry來(lái)組成的path(事件進(jìn)行冒泡的路徑揖铜,為什么是通過冒泡后面會(huì)有解釋)茴丰,HitTestEntry是每一個(gè)命中的入口,它只有一個(gè)HitTestTarget target字段天吓,而HitTestTarget又是由RenderObject來(lái)實(shí)現(xiàn)的贿肩,所以HitTestResult其實(shí)就是一系列通過命中測(cè)試的RenderObject的集合。我們來(lái)看看是如何來(lái)確定命中測(cè)試的結(jié)果的

void hitTest(HitTestResult result, Offset position) {
  assert(renderView != null);
  renderView.hitTest(result, position: position);
  super.hitTest(result, position);//這里是調(diào)用的GestureBinding中的hitTest方法,將WidgetsFlutterBinding加入到最后面
}

調(diào)用renderView的hitTest方法龄寞,繼續(xù)跟進(jìn)

bool hitTest(HitTestResult result, { Offset position }) {
  if (child != null)
    child.hitTest(result, position: position);
  result.add(HitTestEntry(this));
  return true;
}

可以看到renderView并沒有直接實(shí)現(xiàn)HitTestable中的hitTest方法汰规,renderView的hitTest方法中的{ Offset position }是一個(gè)可選參數(shù),并且?guī)б粋€(gè)bool類型的返回值物邑,renderView的hitTest方法顯示對(duì)child進(jìn)行命中測(cè)試溜哮,讓后再將自己添加到命中測(cè)試結(jié)果。

RenderObject中并沒有發(fā)現(xiàn)hitTest方法色解,但是再其子類RenderBox中發(fā)現(xiàn)了名為hitTest的方法茂嗓,也沒有直接實(shí)現(xiàn)HitTestable中的hitTest方法,{ Offset position }也是一個(gè)可選參數(shù)科阎,也有一個(gè)bool類型的返回值

bool hitTest(HitTestResult result, { @required Offset position }) {
  if (_size.contains(position)) {//確定hit的位置再自己的size范圍里面
    if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
      //先對(duì)children進(jìn)行hitTest述吸,然后再對(duì)自己進(jìn)行hitTest,有一項(xiàng)返回true才能將自己添加到HitTestResult里面
      result.add(BoxHitTestEntry(this, position));
      return true;
    }
  }
  return false;
}

我們看一個(gè)比較簡(jiǎn)單的例子锣笨,RenderPadding中是怎樣對(duì)children進(jìn)行命中測(cè)試的蝌矛,RenderPadding的hitTestChildren實(shí)現(xiàn)在RenderShiftedBox中,hitTestSelf的實(shí)現(xiàn)在RenderBox中

@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
  if (child != null) {
    final BoxParentData childParentData = child.parentData;
    return child.hitTest(result, position: position - childParentData.offset);//將點(diǎn)擊點(diǎn)減去偏移應(yīng)用到child的命中測(cè)試
  }
  return false;
}

@protected
bool hitTestSelf(Offset position) => false;//自己進(jìn)行命中測(cè)試

RenderPadding的命中測(cè)試結(jié)果就是如果child命中測(cè)試成功票唆,則自己也會(huì)被添加的命中測(cè)試結(jié)果中,否則就不對(duì)自己進(jìn)行命中測(cè)試

分發(fā)Event

接下來(lái)就是對(duì)Event的分發(fā)了屹徘,我們直接看GestureBinding中的dispatchEvent方法

@override // from HitTestDispatcher
void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
  assert(!locked);
  //沒有命中測(cè)試信息意味著PointerEvent是Hover走趋,Added,Removed其中一種
  if (hitTestResult == null) {
    assert(event is PointerHoverEvent || event is PointerAddedEvent || event is PointerRemovedEvent);
    try {
      //將事件分發(fā)到注冊(cè)了此次事件的路由噪伊,一般是由GestureRecognizer中void addPointer(PointerDownEvent event)方法進(jìn)行注冊(cè)
      pointerRouter.route(event);
    } catch (exception, stack) {}
    return;//進(jìn)行路由分發(fā)直接返回
  }
  //對(duì)命中測(cè)試的結(jié)果進(jìn)行遍歷簿煌,應(yīng)為是先對(duì)child進(jìn)行命中測(cè)試氮唯,所以事件的序列是冒泡向上傳遞的
  for (HitTestEntry entry in hitTestResult.path) {
    try {
      //調(diào)用target的handleEvent方法處理事件
      entry.target.handleEvent(event, entry);
    } catch (exception, stack) {}
  }
}

當(dāng)命中測(cè)試結(jié)果為空時(shí)進(jìn)行路由分發(fā),當(dāng)命中測(cè)試結(jié)果不為空時(shí)姨伟,就進(jìn)行命中結(jié)果分發(fā)惩琉,handleEvent方法的實(shí)現(xiàn)我們來(lái)看一個(gè)比較典型的,RenderPointerListener中的handleEvent夺荒,RenderPointerListener時(shí)Listener(可以監(jiān)聽原始PointEvent的Widget)對(duì)應(yīng)的RenderObject對(duì)象

@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
  assert(debugHandleEvent(event, entry));
  // onPointerEnter, onPointerHover, and onPointerExit events都在MouseTracker里面處理
  if (onPointerDown != null && event is PointerDownEvent)
    return onPointerDown(event);
  if (onPointerMove != null && event is PointerMoveEvent)
    return onPointerMove(event);
  if (onPointerUp != null && event is PointerUpEvent)
    return onPointerUp(event);
  if (onPointerCancel != null && event is PointerCancelEvent)
    return onPointerCancel(event);
}

RenderPointerListener直接把事件給到對(duì)應(yīng)的回調(diào)瞒渠,大多數(shù)RenderObject都沒有實(shí)現(xiàn)handleEvent方法。

事件監(jiān)聽

Flutter的官方文檔推薦我們使用GestureDetector來(lái)檢測(cè)用戶手勢(shì)輸入技扼,GestureDetector幫我們區(qū)別了各種類型的手勢(shì)伍玖,我們只需要設(shè)置需要監(jiān)聽的手勢(shì)回調(diào)就可以了,使用非常方便剿吻。
從我們上面的分析可以看到窍箍,事件的產(chǎn)生與分發(fā),我們來(lái)看一下GestureDetector是如何監(jiān)聽事件并進(jìn)行區(qū)別手勢(shì)的呢丽旅?

@override
Widget build(BuildContext context) {
  final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
    ...省略若干代碼
  return RawGestureDetector(
    gestures: gestures,
    behavior: behavior,
    excludeFromSemantics: excludeFromSemantics,
    child: child,
  );
}

可以看到GestureDetector是通過RawGestureDetector來(lái)實(shí)現(xiàn)的椰棘,我們?cè)倏碦awGestureDetector

@override
Widget build(BuildContext context) {
  Widget result = Listener(
    onPointerDown: _handlePointerDown,
    behavior: widget.behavior ?? _defaultBehavior,
    child: widget.child
  );
  if (!widget.excludeFromSemantics)
    result = _GestureSemantics(owner: this, child: result);
  return result;
}

而RawGestureDetector又是通過Listener來(lái)實(shí)現(xiàn)的,上面我們知道Listener是監(jiān)聽初始事件PointerEvent的榄笙,那他是如何被區(qū)別為各種各樣的手勢(shì)的呢?

手勢(shì)識(shí)別

看一下RawGestureDetector中的_handlePointerDown方法

void _handlePointerDown(PointerDownEvent event) {
  assert(_recognizers != null);
  for (GestureRecognizer recognizer in _recognizers.values)
    recognizer.addPointer(event);
}

在PointerDownEvent的時(shí)候邪狞,將所有recognizer進(jìn)行addPointer(event),繼續(xù)跟進(jìn)addPointer方法办斑,在GestureRecognizer中是空實(shí)現(xiàn)外恕,我們先看一個(gè)簡(jiǎn)單的實(shí)現(xiàn)TapGestureRecognizer

@override
void addPointer(PointerDownEvent event) {
  startTrackingPointer(event.pointer);//開始追蹤id為pointer的事件序列
  if (state == GestureRecognizerState.ready) {
    state = GestureRecognizerState.possible;
    primaryPointer = event.pointer;
    initialPosition = event.position;
    if (deadline != null)
      _timer = Timer(deadline, didExceedDeadline);
  }
}

addPointer中最主要的方法就是startTrackingPointer方法,這個(gè)方法是OneSequenceGestureRecognizer中的乡翅,可以讓OneSequenceGestureRecognizer去追蹤這個(gè)事件序列鳞疲,具體分析在下面手勢(shì)競(jìng)技中再講,Recognizer追蹤了這個(gè)事件序列后蠕蚜,這個(gè)事件的后續(xù)事件都會(huì)被這個(gè)Recognizer處理尚洽,會(huì)觸發(fā)handleEvent方法,通過一系列判斷會(huì)走到handlePrimaryPointer方法靶累,然后再PointerUpEvent時(shí)觸發(fā)
相關(guān)回調(diào)

@override
void handlePrimaryPointer(PointerEvent event) {
  if (event is PointerUpEvent) {
    _finalPosition = event.position;
    _checkUp();
  } else if (event is PointerCancelEvent) {
    _reset();
  }
}

void _checkUp() {
  if (_wonArenaForPrimaryPointer && _finalPosition != null) {
    resolve(GestureDisposition.accepted);
    if (!_wonArenaForPrimaryPointer || _finalPosition == null) {
      return;
    }
    if (onTapUp != null)
      invokeCallback<void>('onTapUp', () { onTapUp(TapUpDetails(globalPosition: _finalPosition)); });//觸發(fā)onTapUp回調(diào)
    if (onTap != null)
      invokeCallback<void>('onTap', onTap);//觸發(fā)onTap回調(diào)
    _reset();
  }
}

Recognizer會(huì)對(duì)事件進(jìn)行分析腺毫,然后會(huì)去區(qū)別不同的情況去觸發(fā)不同的回調(diào)。

如果一個(gè)事件序列被多個(gè)Recognizer追蹤挣柬,比如需要監(jiān)聽用戶點(diǎn)擊與滑動(dòng)潮酒,那么怎么去區(qū)別用戶到底是點(diǎn)擊還是滑動(dòng)呢?

手勢(shì)競(jìng)技

我們先看一下Recognizer是如何追蹤事件序列的邪蛔,先看startTrackingPointer方法

@protected
void startTrackingPointer(int pointer) {
  GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent);
  _trackedPointers.add(pointer);
  assert(!_entries.containsValue(pointer));
  _entries[pointer] = _addPointerToArena(pointer);
}
  1. GestureBinding(實(shí)例是WidgetsFlutterBinding)中的PointerRouter急黎,其實(shí)就是維護(hù)了一個(gè)Map<int,LinkedHashSet<PointerRoute>> routerMap(路由表)的屬性,PointerRoute其實(shí)就是void Function(PointerEvent event)類型的回調(diào)。從上面可以看到勃教,將pointer作為key淤击,handleEvent方法作為值傳入。
  2. _trackedPointers是一個(gè)HashSet<int>故源,記錄此Recognizer追蹤的事件序列
  3. _entries是一個(gè)Map<int, GestureArenaEntry>污抬,GestureArenaEntry中包含一個(gè)GestureArenaManager(手勢(shì)競(jìng)技管理類)、_pointer(事件id)绳军、GestureArenaMember(GestureRecognizer的基類印机,其實(shí)就是Recognizer本身)

繼續(xù)看一下_addPointerToArena方法

GestureArenaEntry _addPointerToArena(int pointer) {
  if (_team != null)
    return _team.add(pointer, this);
  return GestureBinding.instance.gestureArena.add(pointer, this);
}

可以看到GestureBinding(實(shí)例是WidgetsFlutterBinding)中的gestureArena字段(它是GestureArenaManager)將這個(gè)Recognizer添加進(jìn)去∩玖澹看看其add方法

final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};

GestureArenaEntry add(int pointer, GestureArenaMember member) {
  //_GestureArena中是一個(gè)GestureArenaMember的list耳贬,其實(shí)就是一個(gè)手勢(shì)id,對(duì)應(yīng)多個(gè)GestureArenaMember
  final _GestureArena state = _arenas.putIfAbsent(pointer, () {
    return _GestureArena();
  });
  state.add(member);//將GestureArenaMember添加到_GestureArena中
  return GestureArenaEntry._(this, pointer, member);//再返回一個(gè)GestureArenaEntry對(duì)象給Recognizer中entries持有
}

從以上的分析可以總結(jié)一下startTrackingPointer主要做的事情:

  1. 跟據(jù)手勢(shì)的id(pointer)來(lái)添加路由猎唁,此Recognizer就可以接受處理余下的事件序列咒劲,當(dāng)有余下事件序列發(fā)送過來(lái)就會(huì)調(diào)用此Recognizer中的handleEvent方法(此功能由GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent)代碼實(shí)現(xiàn))
  2. 將Recognizer添加到手勢(shì)競(jìng)技場(chǎng),處理同一個(gè)手勢(shì)id的Recognizer將被添加到同一個(gè)_GestureArena(手勢(shì)競(jìng)技場(chǎng))中诫隅,(此功能由_addPointerToArena方法實(shí)現(xiàn))

上面只是添加到路由以及競(jìng)技場(chǎng)腐魂,但是我們還不知道是事件是怎樣被發(fā)送到指定的路由,以及多個(gè)手勢(shì)識(shí)別器是如何競(jìng)爭(zhēng)處理手勢(shì)事件的

事件路由到指定Recognizer

我們知道事件的傳遞是通過冒泡來(lái)進(jìn)行傳遞的逐纬,HitTestResult的最上層是WidgetsFlutterBinding蛔屹,最后處理事件的應(yīng)該在GestureBinding中,我們看一下GestureBinding的handleEvent方法

@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
  pointerRouter.route(event);//調(diào)用PointerRouter的route方法將事件進(jìn)行路由
  ...省略
}

GestureBinding的dispatchEvent方法在HitTestResult為null的時(shí)候才路由豁生,主要也就是Hover兔毒,Added,Removed這三種事件進(jìn)行路由甸箱,而處理這三個(gè)的是一個(gè)global的MouseTracker育叁。而此處路由會(huì)處理所有注冊(cè)到routerMap中的Recognizer(實(shí)際上只是其handleEvent方法)。

多個(gè)Recognizer競(jìng)技

還是看GestureBinding的handleEvent方法

@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);
  }
}

通過pointerRouter路由之后芍殖,在PointerDownEvent時(shí)候就調(diào)用GestureArenaManager的close方法(防止其他Recognizer加入到手勢(shì)競(jìng)技中)豪嗽,所以在就是為什么addPoint注冊(cè)路由的方法需要PointerDownEvent作為參數(shù)了,一旦在down的時(shí)候不注冊(cè)豌骏,那么這個(gè)事件就與你的Recognizer無(wú)關(guān)了龟梦。看一下close方法

void close(int pointer) {
  final _GestureArena state = _arenas[pointer];
  if (state == null)
    return;
  state.isOpen = false;
  _tryToResolveArena(pointer, state);
}

先將isOpen變?yōu)閒alse(在add的時(shí)候首先就會(huì)判斷isOpen)窃躲,然后調(diào)用_tryToResolveArena方法计贰,繼續(xù)跟進(jìn)

void _tryToResolveArena(int pointer, _GestureArena state) {
  assert(_arenas[pointer] == state);
  assert(!state.isOpen);
  if (state.members.length == 1) {//只有1個(gè)Recognizer,直接添加一個(gè)_resolveByDefault方法調(diào)用的task
    scheduleMicrotask(() => _resolveByDefault(pointer, state));
  } else if (state.members.isEmpty) {//沒有Recognizer蒂窒,直接移除該pointer對(duì)應(yīng)的_GestureArena
    _arenas.remove(pointer);
  } else if (state.eagerWinner != null) {//渴望的勝利者躁倒,在_GestureArena關(guān)閉的時(shí)候如果不為空會(huì)直接作為勝利者
    _resolveInFavorOf(pointer, state, state.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);//移除競(jìng)技場(chǎng)赎婚,已經(jīng)得出結(jié)果不需要了
  for (GestureArenaMember rejectedMember in state.members) {
    if (rejectedMember != member)
      rejectedMember.rejectGesture(pointer);//將不是勝利者的GestureArenaMember全部調(diào)用拒絕手勢(shì)
  }
  member.acceptGesture(pointer);//調(diào)用勝利這的接受手勢(shì)
}

但是我們?cè)跊]有eagerWinner的時(shí)候是怎樣來(lái)競(jìng)技的呢?我們用兩個(gè)具體的手勢(shì)識(shí)別器點(diǎn)擊(TapGestureRecognizer)樱溉、滑動(dòng)(PanGestureRecognizer)來(lái)分析

首先我們滑動(dòng)一下

經(jīng)過上面的分析,在down的時(shí)候是解析不出勝利者的纬凤,后續(xù)move事件會(huì)路由給TapGestureRecognizer福贞,PanGestureRecognizer,我們需要看一下TapGestureRecognizer的handleEvent方法

@override
void handleEvent(PointerEvent event) {
  assert(state != GestureRecognizerState.ready);
  if (event.pointer == primaryPointer) {
    //接受手勢(shì)前滑動(dòng)距離是否溢出容忍值
    final bool isPreAcceptSlopPastTolerance =
        state == GestureRecognizerState.possible &&
        preAcceptSlopTolerance != null &&
        _getDistance(event) > preAcceptSlopTolerance;
    //接受手勢(shì)后滑動(dòng)距離是否溢出容忍值
    final bool isPostAcceptSlopPastTolerance =
        state == GestureRecognizerState.accepted &&
        postAcceptSlopTolerance != null &&
        _getDistance(event) > postAcceptSlopTolerance;
    //move事件下停士,超出容忍值
    if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
      resolve(GestureDisposition.rejected);//拒絕后續(xù)事件
      stopTrackingPointer(primaryPointer);//結(jié)束追蹤事件
    } else {
      handlePrimaryPointer(event);
    }
  }
  stopTrackingIfPointerNoLongerDown(event);
}

所以但凡我們滑動(dòng)的距離超出了容忍值(這個(gè)值是根據(jù)經(jīng)驗(yàn)事件來(lái)確定的值)挖帘,都會(huì)拒絕事件結(jié)束追蹤。所以事件就會(huì)落到PanGestureRecognizer身上恋技。

我們?cè)冱c(diǎn)一下

我們需要看一下PanGestureRecognizer的handleEvent方法

@override
void handleEvent(PointerEvent event) {
  assert(_state != _DragState.ready);
  if (!event.synthesized
      && (event is PointerDownEvent || event is PointerMoveEvent)) {
    final VelocityTracker tracker = _velocityTrackers[event.pointer];
    assert(tracker != null);
    tracker.addPosition(event.timeStamp, event.position);
  }

  if (event is PointerMoveEvent) {
    final Offset delta = event.delta;
    if (_state == _DragState.accepted) {
      if (onUpdate != null) {
        invokeCallback<void>('onUpdate', () => onUpdate(DragUpdateDetails(
          sourceTimeStamp: event.timeStamp,
          delta: _getDeltaForDetails(delta),
          primaryDelta: _getPrimaryValueFromOffset(delta),
          globalPosition: event.position,
        )));
      }
    } else {//move事件拇舀,沒有接受事件時(shí)
      _pendingDragOffset += delta;
      _lastPendingEventTimestamp = event.timeStamp;
      //判斷是否由足夠的滑動(dòng)距離來(lái)接受,也就是說滑動(dòng)距離超過一定距離會(huì)主動(dòng)接受
      if (_hasSufficientPendingDragDeltaToAccept)
        resolve(GestureDisposition.accepted);
    }
  }
  stopTrackingIfPointerNoLongerDown(event);//up事件的時(shí)候會(huì)停止追蹤
}

由于我們是點(diǎn)一下蜻底,那么距離不夠是不會(huì)去主動(dòng)接受的骄崩,等經(jīng)過一系列move事件結(jié)束后PanGestureRecognizer還是沒有獲得事件,最后再up的時(shí)候就停止追蹤事件了薄辅,那么事件就會(huì)落到TapGestureRecognizer身上要拂。

通過上面兩種情況的分析,不同的Recognizer都有自己的邏輯去接受站楚、拒絕脱惰、停止追蹤事件。

接受窿春、拒絕拉一、停止追蹤事件

通過resolve方法傳入一個(gè)GestureDisposition可以讓Recognizer來(lái)處置事件,我們跟進(jìn)resolve方法看一下具體操作旧乞,

void resolve(GestureDisposition disposition) {
  final List<GestureArenaEntry> localEntries = List<GestureArenaEntry>.from(_entries.values);
  _entries.clear();
  for (GestureArenaEntry entry in localEntries)
    entry.resolve(disposition);
}

void resolve(GestureDisposition disposition) {
  _arena._resolve(_pointer, _member, disposition);
}

void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
  final _GestureArena state = _arenas[pointer];
  if (state == null)
    return; 
  assert(state.members.contains(member));
  if (disposition == GestureDisposition.rejected) {
    state.members.remove(member);//移除一個(gè)Recognizer
    member.rejectGesture(pointer);//調(diào)用其rejectGesture方法
    if (!state.isOpen)
      _tryToResolveArena(pointer, state);//嘗試確定勝利者
  } else {
    if (state.isOpen) {
      state.eagerWinner ??= member;
    } else {
      _resolveInFavorOf(pointer, state, member);//確定勝利者
    }
  }
}

可以看到如果是接受蔚润,就直接確認(rèn)勝利者,如果是拒絕良蛮,就將其踢出并嘗試確認(rèn)勝利者抽碌。再看一下stopTrackingPointer的具體操作

void stopTrackingPointer(int pointer) {
  if (_trackedPointers.contains(pointer)) {
    GestureBinding.instance.pointerRouter.removeRoute(pointer, handleEvent);//移除路由,不處理余下事件
    _trackedPointers.remove(pointer);//從_trackedPointers中移除
    if (_trackedPointers.isEmpty)
      didStopTrackingLastPointer(pointer);
  }
}

主要就是移除路由决瞳,didStopTrackingLastPointer是在沒有追蹤的PointEvent時(shí)货徙,做一些收尾工作,具體都有不同實(shí)現(xiàn)皮胡。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末痴颊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子屡贺,更是在濱河造成了極大的恐慌蠢棱,老刑警劉巖锌杀,帶你破解...
    沈念sama閱讀 212,294評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異泻仙,居然都是意外死亡糕再,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,493評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門玉转,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)突想,“玉大人,你說我怎么就攤上這事究抓』#” “怎么了?”我有些...
    開封第一講書人閱讀 157,790評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵刺下,是天一觀的道長(zhǎng)绑嘹。 經(jīng)常有香客問我,道長(zhǎng)橘茉,這世上最難降的妖魔是什么工腋? 我笑而不...
    開封第一講書人閱讀 56,595評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮畅卓,結(jié)果婚禮上夷蚊,老公的妹妹穿的比我還像新娘。我一直安慰自己髓介,他們只是感情好惕鼓,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,718評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著唐础,像睡著了一般箱歧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上一膨,一...
    開封第一講書人閱讀 49,906評(píng)論 1 290
  • 那天呀邢,我揣著相機(jī)與錄音,去河邊找鬼豹绪。 笑死价淌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瞒津。 我是一名探鬼主播蝉衣,決...
    沈念sama閱讀 39,053評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼巷蚪!你這毒婦竟也來(lái)了病毡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,797評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤屁柏,失蹤者是張志新(化名)和其女友劉穎啦膜,沒想到半個(gè)月后有送,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,250評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡僧家,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,570評(píng)論 2 327
  • 正文 我和宋清朗相戀三年雀摘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片八拱。...
    茶點(diǎn)故事閱讀 38,711評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡届宠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乘粒,到底是詐尸還是另有隱情,我是刑警寧澤伤塌,帶...
    沈念sama閱讀 34,388評(píng)論 4 332
  • 正文 年R本政府宣布灯萍,位于F島的核電站,受9級(jí)特大地震影響每聪,放射性物質(zhì)發(fā)生泄漏旦棉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,018評(píng)論 3 316
  • 文/蒙蒙 一药薯、第九天 我趴在偏房一處隱蔽的房頂上張望绑洛。 院中可真熱鬧,春花似錦童本、人聲如沸真屯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,796評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)绑蔫。三九已至,卻和暖如春泵额,著一層夾襖步出監(jiān)牢的瞬間配深,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,023評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工嫁盲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留篓叶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,461評(píng)論 2 360
  • 正文 我出身青樓羞秤,卻偏偏與公主長(zhǎng)得像缸托,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瘾蛋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,595評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 在開發(fā)過程中嗦董,大家或多或少的都會(huì)碰到令人頭疼的手勢(shì)沖突問題,正好前兩天碰到一個(gè)類似的bug瘦黑,于是借著這個(gè)機(jī)會(huì)了解了...
    閆仕偉閱讀 5,303評(píng)論 2 23
  • 觸摸事件的生命周期 當(dāng)我們手指觸碰屏幕的那一刻京革,一個(gè)觸摸事件便產(chǎn)生了奇唤。經(jīng)過進(jìn)程間通信,觸摸事件被傳遞到合適的應(yīng)用之...
    Gintok閱讀 1,344評(píng)論 0 3
  • 在iOS開發(fā)中經(jīng)常會(huì)涉及到觸摸事件匹摇。本想自己總結(jié)一下咬扇,但是遇到了這篇文章,感覺總結(jié)的已經(jīng)很到位廊勃,特此轉(zhuǎn)載懈贺。作者:L...
    WQ_UESTC閱讀 5,995評(píng)論 4 26
  • 本文主要講解iOS觸摸事件的一系列機(jī)制,涉及的問題大致包括: 觸摸事件由觸屏生成后如何傳遞到當(dāng)前應(yīng)用坡垫? 應(yīng)用接收觸...
    baihualinxin閱讀 1,203評(píng)論 0 9
  • 1.寫作緣起 在觸摸事件傳遞機(jī)制這個(gè)的問題上連自己都覺著不就是老掉牙的Hit-Testingt么,遞歸遍歷,找到最...
    大愛無(wú)言閱讀 10,461評(píng)論 13 57