手势竞技场是用来解决多个手势歧义的;

竞技相关的类如下⬇️

Flutter事件之手势竞技场

_GestrueArena

竞技场的实体类,该类有五个成员变量,一个办法

Flutter事件之手势竞技场

成员变量

  • isOpen:表明该竞技场是否开放
  • isHeld:表明该竞技场是否挂起
  • hasPendingSweep:表明该竞技场是否等候清扫
  • eagerWinner:巴望成功的参赛者
  • members:竞技场的成员(参赛者)

办法

add(GestureArenaMember member)

添加成员到竞技场

void add(GestureArenaMember member) {
  assert(isOpen); //添加时,竞技场一定是翻开的
  members.add(member);
}

GestureArenaEntry

竞技场信息发送器,该类是私有类,只能在这个文件里被结构

Flutter事件之手势竞技场

成员变量

  • _arena:竞技场办理者
  • _member:竞技场成员
  • pointer:触点id

办法

resolve(GestureDisposition disposition)

向竞技场办理者发送信息(成功或许失利)的接口类,这个类的在GestureArenaManager.add中创立的

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

GestureDisposition是一个枚举类型⬇️,别离表明成功、失利;

Flutter事件之手势竞技场

GestureArenaMember

Flutter事件之手势竞技场

这个类是一个笼统类,有两个笼统办法,别离表明成功回调和失利回调;

GestureRecognizer承继自该类,完结这两个办法以接收竞技成果。

GestureArenaManager

竞技场的办理者,办理多个竞技场,在整个Flutter生命周期内只有一个实例,保存在GestureBinding类中

Flutter事件之手势竞技场

成员变量

_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):

  1. TapGestureRecognizer和LongPressGestureRecognizer会别离进行触点注册(addAllowedPointer),在触点注册时会创立竞技场、GestureArenaEntry目标,并把自己加入竞技场,这样竞技场内成员就有他们两个了;LongPressGestureRecognizer会在注册时开启一个500ms的定时器;

  2. 此刻履行GestureBinding.handleEvent办法,pointerRouter.route(event)这儿会调用手势辨认器的handleEvent,可是在这儿并没有判决的办法,所以咱们忽略退出;

  3. 随后履行gestureArena.close(event.pointer);,前面咱们讲过在这儿会封闭竞技场调用_tryToResolveArena办法测验判决,可是看条件,这儿并不会判决出成果,所以也会退出。这样按下事情的GestureBinding.handleEvent履行完了。

  4. 此刻长按手势的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能看到相关完结。