/// 检测手势的widget。
/// A widget that detects gestures.
///
/// 尝试识别与其非空回调相对应的手势。
/// Attempts to recognize gestures that correspond to its non-null callbacks.
///
/// 如果此widget有一个child,它将遵循该child的大小调整行为。
/// 如果它没有child,它就会成长以适应parent。
/// If this widget has a child, it defers to that child for its sizing behavior.
/// If it does not have a child, it grows to fit the parent instead.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=WhVXkCFPmK4}
///
/// 默认情况下,具有不可见子项的 GestureDetector 会忽略触摸;这种行为可以通过[行为]来控制。
/// By default a GestureDetector with an invisible child ignores touches;
/// this behavior can be controlled with [behavior].
///
/// GestureDetector 还侦听辅助功能事件并将它们映射到回调。
/// 要忽略辅助功能事件,请将 [excludeFromSemantics] 设置为 true。
/// GestureDetector also listens for accessibility events and maps
/// them to the callbacks. To ignore accessibility events, set
/// [excludeFromSemantics] to true.
///
/// See <http://flutter.dev/gestures/> for additional information.
///
/// Material design应用程序通常会对触摸做出反应,并产生墨水飞溅效果。
/// [InkWell] 类实现了此效果,并且可以用来代替 [GestureDetector] 来处理点击。
/// Material design applications typically react to touches with ink splash
/// effects. The [InkWell] class implements this effect and can be used in place
/// of a [GestureDetector] for handling taps.
///
/// {@tool dartpad}
/// 此示例包含一个包裹在 [GestureDetector] 中的黑灯泡。
/// 通过设置 `_lights` 字段,当点击“打开灯”按钮时,灯泡会变成黄色,而当点击“关闭灯”时,灯泡会再次关闭。
/// This example contains a black light bulb wrapped in a [GestureDetector]. It
/// turns the light bulb yellow when the "TURN LIGHT ON" button is tapped by
/// setting the `_lights` field, and off again when "TURN LIGHT OFF" is tapped.
///
/// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.0.dart **
/// {@end-tool}
///
/// {@tool dartpad}
/// 此示例使用 [Container] 包装 [GestureDetector],用于检测点击。
/// This example uses a [Container] that wraps a [GestureDetector] widget which
/// detects a tap.
///
/// 由于 [GestureDetector] 没有child,因此它采用其父项的大小,从而使周围 [Container] 的整个区域都可单击。
/// 点击时,[Container] 通过设置 `_color` 字段变成黄色。再次点击时,它会变回白色。
/// Since the [GestureDetector] does not have a child, it takes on the size of its
/// parent, making the entire area of the surrounding [Container] clickable. When
/// tapped, the [Container] turns yellow by setting the `_color` field. When
/// tapped again, it goes back to white.
///
/// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.1.dart **
/// {@end-tool}
///
/// ### 故障排除
/// ### Troubleshooting
///
/// 为什么我的父 [GestureDetector.onTap] 方法没有被调用?
/// Why isn't my parent [GestureDetector.onTap] method called?
///
/// 给定具有 onTap 回调的父 [GestureDetector] 和也定义 onTap 回调的子 GestureDetector,当点击内部 GestureDetector 时,两个 GestureDetector 都会向手势区域发送 [GestureRecognizer]。
/// 这是因为指针坐标位于两个手势检测器的范围内。
/// 在这种情况下,子 GestureDetector 获胜,因为它是第一个进入竞技场的,解决了先到先得的问题。
/// 子级的 onTap 被调用,而父级的 onTap 则没有,因为手势已被消耗。
/// Given a parent [GestureDetector] with an onTap callback, and a child
/// GestureDetector that also defines an onTap callback, when the inner
/// GestureDetector is tapped, both GestureDetectors send a [GestureRecognizer]
/// into the gesture arena. This is because the pointer coordinates are within the
/// bounds of both GestureDetectors. The child GestureDetector wins in this
/// scenario because it was the first to enter the arena, resolving as first come,
/// first served. The child onTap is called, and the parent's is not as the gesture has
/// been consumed.
/// For more information on gesture disambiguation see:
/// [Gesture disambiguation](https://docs.flutter.dev/development/ui/advanced/gestures#gesture-disambiguation).
///
/// 将 [GestureDetector.behavior] 设置为 [HitTestBehavior.opaque] 或 [HitTestBehavior.translucent] 对父子关系没有影响:
/// 两个 GestureDetector 都将 GestureRecognizer 发送到手势竞技场,只有一个获胜。
/// Setting [GestureDetector.behavior] to [HitTestBehavior.opaque]
/// or [HitTestBehavior.translucent] has no impact on parent-child relationships:
/// both GestureDetectors send a GestureRecognizer into the gesture arena, only one wins.
///
/// 一些回调(例如 onTapDown)可以在识别器赢得竞技场之前触发,而其它回调(例如 onTapCancel)即使在失去竞技场时也会触发。
/// 因此,上例中的父检测器可能会调用它的一些回调,即使它在竞技场中失败。
/// Some callbacks (e.g. onTapDown) can fire before a recognizer wins the arena,
/// and others (e.g. onTapCancel) fire even when it loses the arena. Therefore,
/// the parent detector in the example above may call some of its callbacks even
/// though it loses in the arena.
///
/// {@tool dartpad}
/// 此示例使用包装绿色 [Container] 的 [GestureDetector] 和包装黄色容器的第二个 GestureDetector。
/// 第二个 GestureDetector 是绿色 Container 的子级。
/// 两个 GestureDetector 都定义了 onTap 回调。当调用回调时,它会向相应的容器添加红色边框。
/// This example uses a [GestureDetector] that wraps a green [Container] and a second
/// GestureDetector that wraps a yellow Container. The second GestureDetector is
/// a child of the green Container.
/// Both GestureDetectors define an onTap callback. When the callback is called it
/// adds a red border to the corresponding Container.
///
/// 当点击绿色容器时,它的父 GestureDetector 进入手势区域。
/// 它获胜是因为没有竞争的 GestureDetector 并且绿色容器显示红色边框。
/// When the green Container is tapped, it's parent GestureDetector enters
/// the gesture arena. It wins because there is no competing GestureDetector and
/// the green Container shows a red border.
/// 当点击黄色容器时,它的父 GestureDetector 进入手势区域。
/// 包裹绿色容器的 GestureDetector 也进入手势区域(指针事件坐标位于两个 GestureDetector 边界内)。
/// 包裹黄色容器的 GestureDetector 获胜,因为它是第一个进入竞技场的检测器。
/// When the yellow Container is tapped, it's parent GestureDetector enters
/// the gesture arena. The GestureDetector that wraps the green Container also
/// enters the gesture arena (the pointer events coordinates are inside both
/// GestureDetectors bounds). The GestureDetector that wraps the yellow Container
/// wins because it was the first detector to enter the arena.
///
/// 此示例将 [debugPrintGestureArenaDiagnostics] 设置为 true。该标志打印有关手势区域的有用信息。
/// This example sets [debugPrintGestureArenaDiagnostics] to true.
/// This flag prints useful information about gesture arenas.
///
/// 将 [GestureDetector.behavior] 属性更改为 [HitTestBehavior.translucent] 或 [HitTestBehavior.opaque] 没有影响:两个 GestureDetector 都将 [GestureRecognizer] 发送到手势区域,只有一个获胜。
/// Changing the [GestureDetector.behavior] property to [HitTestBehavior.translucent]
/// or [HitTestBehavior.opaque] has no impact: both GestureDetectors send a [GestureRecognizer]
/// into the gesture arena, only one wins.
///
/// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.2.dart **
/// {@end-tool}
///
/// ## 调试
/// ## Debugging
///
/// 要查看 [GestureDetector] 的命中测试框有多大以进行调试,请将 [debugPaintPointersEnabled] 设置为 true。
/// To see how large the hit test box of a [GestureDetector] is for debugging
/// purposes, set [debugPaintPointersEnabled] to true.
classGestureDetectorextendsStatelessWidget{GestureDetector({super.key,this.child,this.onTapDown,this.onTapUp,this.onTap,this.onTapCancel,this.onSecondaryTap,this.onSecondaryTapDown,this.onSecondaryTapUp,this.onSecondaryTapCancel,this.onTertiaryTapDown,this.onTertiaryTapUp,this.onTertiaryTapCancel,this.onDoubleTapDown,this.onDoubleTap,this.onDoubleTapCancel,this.onLongPressDown,this.onLongPressCancel,this.onLongPress,this.onLongPressStart,this.onLongPressMoveUpdate,this.onLongPressUp,this.onLongPressEnd,this.onSecondaryLongPressDown,this.onSecondaryLongPressCancel,this.onSecondaryLongPress,this.onSecondaryLongPressStart,this.onSecondaryLongPressMoveUpdate,this.onSecondaryLongPressUp,this.onSecondaryLongPressEnd,this.onTertiaryLongPressDown,this.onTertiaryLongPressCancel,this.onTertiaryLongPress,this.onTertiaryLongPressStart,this.onTertiaryLongPressMoveUpdate,this.onTertiaryLongPressUp,this.onTertiaryLongPressEnd,this.onVerticalDragDown,this.onVerticalDragStart,this.onVerticalDragUpdate,this.onVerticalDragEnd,this.onVerticalDragCancel,this.onHorizontalDragDown,this.onHorizontalDragStart,this.onHorizontalDragUpdate,this.onHorizontalDragEnd,this.onHorizontalDragCancel,this.onForcePressStart,this.onForcePressPeak,this.onForcePressUpdate,this.onForcePressEnd,this.onPanDown,this.onPanStart,this.onPanUpdate,this.onPanEnd,this.onPanCancel,this.onScaleStart,this.onScaleUpdate,this.onScaleEnd,this.behavior,this.excludeFromSemantics=false,this.dragStartBehavior=DragStartBehavior.start,this.trackpadScrollCausesScale=false,this.trackpadScrollToScaleFactor=kDefaultTrackpadScrollToScaleFactor,this.supportedDevices,}):assert((){finalboolhaveVerticalDrag=onVerticalDragStart!=null||onVerticalDragUpdate!=null||onVerticalDragEnd!=null;finalboolhaveHorizontalDrag=onHorizontalDragStart!=null||onHorizontalDragUpdate!=null||onHorizontalDragEnd!=null;finalboolhavePan=onPanStart!=null||onPanUpdate!=null||onPanEnd!=null;finalboolhaveScale=onScaleStart!=null||onScaleUpdate!=null||onScaleEnd!=null;if(havePan||haveScale){if(havePan&&haveScale){throwFlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Incorrect GestureDetector arguments.'),ErrorDescription('Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.',),ErrorHint('Just use the scale gesture recognizer.'),]);}finalStringrecognizer=havePan?'pan':'scale';if(haveVerticalDrag&&haveHorizontalDrag){throwFlutterError('Incorrect GestureDetector arguments.\n''Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer ''will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.',);}}returntrue;}());}
void_syncAll(Map<Type,GestureRecognizerFactory>gestures){assert(_recognizers!=null);// 将_recognizers赋值给局部变量oldRecognizers
finalMap<Type,GestureRecognizer>oldRecognizers=_recognizers!;// 创建一个新的_recognizers
_recognizers=<Type,GestureRecognizer>{};// 遍历gestures的keys,这里的type就是GestureRecognizer了
for(finalTypetypeingestures.keys){assert(gestures[type]!=null);assert(gestures[type]!._debugAssertTypeMatches(type));assert(!_recognizers!.containsKey(type));// 如果oldRecognizers中存在该GestureRecognizer,则直接赋值即可
// 如果oldRecognizers中不存在该GestureRecognizer,那么调用GestureRecognizerFactoryWithHandlers的constructor方法创建一个新的GestureRecognizer
_recognizers![type]=oldRecognizers[type]??gestures[type]!.constructor();assert(_recognizers![type].runtimeType==type,'GestureRecognizerFactory of type $type created a GestureRecognizer of type ${_recognizers![type].runtimeType}. The GestureRecognizerFactory must be specialized with the type of the class that it returns from its constructor method.');// 执行GestureRecognizerFactoryWithHandlers的initializer方法,此处initializer方法会对外部传入的回调进行赋值操作,例如instance..onTapDown = onTapDown
gestures[type]!.initializer(_recognizers![type]!);}// 遍历oldRecognizers的keys,如果_recognizers不包含该type,也就是不包含某个GestureRecognizer了,那么执行该GestureRecognizer的dispose方法,从而做一些资源释放的工作。
for(finalTypetypeinoldRecognizers.keys){if(!_recognizers!.containsKey(type)){oldRecognizers[type]!.dispose();}}}
@overridevoidaddAllowedPointer(PointerDownEventevent){// 此时state为GestureRecognizerState.ready,这是在父类PrimaryPointerGestureRecognizer中成员变量_state的初始赋值,表示识别器已准备好开始识别手势
if(state==GestureRecognizerState.ready){// 如果之前的手势区域中没有结果,我们将忽略它们并准备接受新的指针。
// If there is no result in the previous gesture arena,
// we ignore them and prepare to accept a new pointer.
// 此时_down为null,_up为null,所以不会执行_reset方法去重置变量状态
if(_down!=null&&_up!=null){assert(_down!.pointer==_up!.pointer);_reset();}assert(_down==null&&_up==null);// 必须在此方法中指定“_down”而不是“handlePrimaryPointer”,因为“acceptGesture”可能会在“handlePrimaryPointer”之前调用,后者依赖“_down”来调用“handleTapDown”。
// `_down` must be assigned in this method instead of `handlePrimaryPointer`,
// because `acceptGesture` might be called before `handlePrimaryPointer`,
// which relies on `_down` to call `handleTapDown`.
_down=event;}if(_down!=null){// 当指针向下(即由于移动)时此点击手势被拒绝时,当添加另一个允许的指针时,会发生这种情况,在这种情况下,所有指针都将被忽略。
// `_down` 为 null 意味着 _reset() 已被调用,因为它总是在第一个允许的 down 事件中设置,并且除了 Reset() 之外不会被清除,
// This happens when this tap gesture has been rejected while the pointer
// is down (i.e. due to movement), when another allowed pointer is added,
// in which case all pointers are ignored. The `_down` being null
// means that _reset() has been called, since it is always set at the
// first allowed down event and will not be cleared except for reset(),
super.addAllowedPointer(event);}}
@override@protectedvoidstartTrackingPointer(intpointer,[Matrix4?transform]){// 当 _down 为空时,识别器不应该跟踪任何指针,因为在这种状态下调用 _checkDown 会抛出异常。
// The recognizer should never track any pointers when `_down` is null,
// because calling `_checkDown` in this state will throw exception.
assert(_down!=null);super.startTrackingPointer(pointer,transform);}
// _arenas以pointer作为Key,_GestureArena作为Value
finalMap<int,_GestureArena>_arenas=<int,_GestureArena>{};/// Adds a new member (e.g., gesture recognizer) to the arena.
GestureArenaEntryadd(intpointer,GestureArenaMembermember){// 如果_arenas中存在该值为pointer的Key,那么返回它的Value,也就是一个_GestureArena
// 否则,以该pointer为Key,新的_GestureArena为Value,创建一个新的Key-Value对存入_arenas中,最后返回它的Value,也就是一个新的_GestureArena
final_GestureArenastate=_arenas.putIfAbsent(pointer,(){assert(_debugLogDiagnostic(pointer,'★ Opening new gesture arena.'));return_GestureArena();});// 将GestureArenaMember添加到_GestureArena中的成员变量members,它是一个List
// 此处的GestureArenaMember其实是我们的识别器
state.add(member);assert(_debugLogDiagnostic(pointer,'Adding: $member'));// 最后返回GestureArenaEntry对象
returnGestureArenaEntry._(this,pointer,member);}
@overridevoidresolve(GestureDispositiondisposition){if(_wonArenaForPrimaryPointer&&disposition==GestureDisposition.rejected){// 如果手势被取消,就会发生这种情况。例如,当指针超过触摸斜率时,按钮已改变,或者识别器被布置。
// This can happen if the gesture has been canceled. For example, when
// the pointer has exceeded the touch slop, the buttons have been changed,
// or if the recognizer is disposed.
assert(_sentTapDown);// 之前触发过handleTapDown的指针最终不会导致点击
// 如果之前已触发过handleTapDown ,则一旦手势失去区域,就会触发此操作
_checkCancel(null,'spontaneous');// 重置成员变量的状态
_reset();}super.resolve(disposition);}
voidclose(intpointer){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);}
// 强制解决竞技场,让第一个成员获胜。
voidsweep(intpointer){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(inti=1;i<state.members.length;i++){state.members[i].rejectGesture(pointer);}}}