注:本文代码基于Flutter SDK 3.13.5

一、前言

在之前解读Flutter源码之Listener一文中,我们已经知道Listener用来监听原始指针事件,它的事件处理流程分为命中测试-事件分发-事件清理三部分。

而本文所讲的GestureDetector,它的内部实际上是对Listener的进一步封装,用以识别语义化的手势。相较于ListenerGestureDetector还会涉及手势冲突,那么相应地就需要了解如何去解决手势冲突。

二、什么是GestureDetector

遇事不决,先来看下GestureDetector的注释&部分源码。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/// 检测手势的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.
class GestureDetector extends StatelessWidget {

  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(() {
         final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null;
         final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null;
         final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;
         final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
         if (havePan || haveScale) {
           if (havePan && haveScale) {
             throw FlutterError.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.'),
             ]);
           }
           final String recognizer = havePan ? 'pan' : 'scale';
           if (haveVerticalDrag && haveHorizontalDrag) {
             throw FlutterError(
               '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.',
             );
           }
         }
         return true;
       }());
}

可以看到,GestureDetector是一个StatelessWidget,当你传入了非空回调,那么它会尝试识别与其非空回调相对应的手势。在注释中,GestureDetector也为开发者提供了好几个示例,这里就不分析了,感兴趣的可以自行研究。

GestureDetector设计了丰富的手势监听回调,这里列出几个较为常见的。

GestureDetector回调 说明
onTap 发生了对主按钮的点击。当点击手势获胜时会触发此操作。如果点击手势没有成功,则会调用onTapCancel
onTapDown 可能导致点击主按钮的指针已接触到屏幕的特定位置。即使尚未选择获胜手势,也会在短暂的超时后调用此方法。如果点击手势获胜,将调用onTapUp ,否则将调用onTapCancel
onTapUp 将触发主按钮点击的指针已停止在特定位置接触屏幕。如果点击手势获胜,则会在onTap之前立即触发。如果点击手势没有成功,则会调用onTapCancel
onTapCancel 先前触发onTapDown的指针最终不会导致点击。如果点击手势未获胜,则在onTapDown之后调用,而不是onTapUponTap
onDoubleTap 用户在同一位置快速连续点击主按钮两次
onDoubleTapDown 可能导致双击的指针已接触屏幕的特定位置。在第二次点击的向下事件后立即触发。如果用户完成双击并且手势获胜,则在此回调之后将调用onDoubleTap。否则, onDoubleTapCancel将在此回调后调用
onDoubleTapCancel 之前触发onDoubleTapDown的指针最终不会导致双击
onLongPress 当识别到主按钮的长按手势时调用。当指针长时间与屏幕同一位置保持接触时触发。这相当于(并在之后立即调用) onLongPressStart。两者之间的唯一区别是此回调不包含指针最初接触屏幕的位置的详细信息。
onLongPressDown 指针已通过主按钮接触屏幕,这可能是长按的开始。这在指针向下事件后触发。如果用户完成长按,并且该手势获胜,则在此回调后将调用onLongPressStart。否则, onLongPressCancel将在该回调之后被调用
onLongPressUp 触发主按钮长按的指针已停止接触屏幕。这相当于(并在之后立即调用) onLongPressEnd。两者之间的唯一区别是,此回调不包含指针停止接触屏幕时的状态详细信息
onLongPressCancel 先前触发onLongPressDown的指针最终不会导致长按。如果先前已触发onLongPressDown则一旦手势丢失,就会触发此操作。如果用户完成了长按,并且手势获胜,则调​​用onLongPressStart和onLongPress
onLongPressStart 当识别到主按钮的长按手势时调用。当指针长时间与屏幕同一位置保持接触时触发。这相当于(并在之前调用) onLongPress。两者之间的唯一区别是,此回调包含指针最初接触屏幕的位置的详细信息,而onLongPress则不包含
onLongPressMoveUpdate 长按主按钮后,指针已被拖动移动
onLongPressEnd 触发主按钮长按的指针已停止接触屏幕。这相当于(并在之前调用) onLongPressUp。两者之间的唯一区别是,此回调包含指针停止接触屏幕时的状态详细信息,而onLongPressUp则不包含
onVerticalDragDown 指针已通过主按钮接触屏幕,并且可能开始垂直移动
onVerticalDragStart 指针已通过主按钮接触屏幕并开始垂直移动
onVerticalDragUpdate 通过主按钮与屏幕接触并垂直移动的指针已沿垂直方向移动
onVerticalDragEnd 之前通过主按钮与屏幕接触并垂直移动的指针不再与屏幕接触,并且在停止接触屏幕时以特定速度移动
onVerticalDragCancel 之前触发onVerticalDragDown的指针没有完成
onHorizontalDragDown 指针已通过主按钮接触屏幕,并且可能开始水平移动
onHorizontalDragStart 指针已通过主按钮接触屏幕并开始水平移动
onHorizontalDragUpdate 通过主按钮与屏幕接触并水平移动的指针已沿水平方向移动
onHorizontalDragEnd 之前通过主按钮与屏幕接触并水平移动的指针不再与屏幕接触,并且在停止接触屏幕时以特定速度移动
onHorizontalDragCancel 先前触发onHorizontalDragDown的指针未完成
onPanDown 指针已通过主按钮接触屏幕并可能开始移动
onPanStart 指针已与主按钮接触到屏幕并开始移动
onPanUpdate 通过主按钮与屏幕接触并移动的指针已再次移动
onPanEnd 之前通过主按钮与屏幕接触并移动的指针不再与屏幕接触,并且在停止接触屏幕时以特定速度移动
onPanCancel 先前触发onPanDown的指针未完成

有的回调包含了指针最初接触屏幕的位置的详细信息,例如onTapDown&onDoubleTapDown回调,TapDownDetailsGestureTapDownCallback的详细信息。

TapDownDetails属性 说明
globalPosition 指针接触屏幕的全局位置
kind 发起事件的设备类型
localPosition 指针接触屏幕的本地位置

三、GestureDetector示例

为了便于分析,在GestureDetector示例中丢弃了runApp方法带来的一个View结构(在实际项目中不能这样做),具体分析看解读Flutter源码之runApp一文

程序运行起来后,通过Flutter Inspector查看Widget层次结构如下所示。

对应的代码为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void main() {
  // 输出手势竞技场的相关日志,用于跟踪手势竞争的执行逻辑
  debugPrintGestureArenaDiagnostics = true;
  _runApp(const MyApp());
}

void _runApp(Widget app) {
  final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
  Timer.run(() {
    binding.attachRootWidget(app);
  });
  binding.scheduleWarmUpFrame();
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: GestureDetector(
        child: const ColoredBox(color: Colors.blueAccent),
        onTap: () => debugPrint('inner onTap'),
        onTapDown: (TapDownDetails details) => debugPrint('inner onTapDown'),
        onTapUp: (TapUpDetails details) => debugPrint('inner onTapUp'),
        onTapCancel: () => debugPrint('inner onTapCancel'),
      ),
      onTap: () => debugPrint('outer onTap'),
      onTapDown: (TapDownDetails details) => debugPrint('outer onTapDown'),
      onTapUp: (TapUpDetails details) => debugPrint('outer onTapUp'),
      onTapCancel: () => debugPrint('outer onTapCancel'),
    );
  }
}

当在屏幕上点击时,打印的日志如下:

1
2
3
I/flutter (22965): inner onTapDown
I/flutter (22965): inner onTapUp
I/flutter (22965): inner onTap

从日志可以知道,为什么父GestureDetectoronTapDown等回调方法没有被调用呢?这里先给出大概的答案,后面再结合源码进行详细分析。

给定具有onTapDown回调的父GestureDetector和也定义了onTapDown回调的子 GestureDetector,当点击内部GestureDetector时,两个GestureDetector都会向手势区域发送GestureRecognizer,这是因为指针坐标位于两个手势检测器的范围内。

在这种情况下,子GestureDetector获胜,因为它是第一个进入竞技场的,解决了先到先得的问题。因此,子级的onTapDown被调用,而父级的onTapDown则没有,因为手势已被消耗。

四、源码分析

4.1、GestureDetectorbuild方法

看下GestureDetectorbuild方法,可以发现它通过GestureRecognizerFactory工厂类定义了GestureRecognizer的创建方法 _constructor 与初始化方法 _initializer,然后把新创建的GestureRecognizerFactoryWithHandlers实例放入gestures这个Map中保存。

GestureRecognizerFactoryWithHandlers执行初始化方法时,GestureRecognizer就会持有外部传入的回调方法,例如TapGestureRecognizer持有onTapDown回调方法。

最后,build方法返回了RawGestureDetector组件,gestures也作为参数传入了RawGestureDetector组件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@override
Widget build(BuildContext context) {
  final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
  final DeviceGestureSettings? gestureSettings = MediaQuery.maybeGestureSettingsOf(context);

  if (onTapDown != null ||
      onTapUp != null ||
      onTap != null ||
      onTapCancel != null ||
      onSecondaryTap != null ||
      onSecondaryTapDown != null ||
      onSecondaryTapUp != null ||
      onSecondaryTapCancel != null||
      onTertiaryTapDown != null ||
      onTertiaryTapUp != null ||
      onTertiaryTapCancel != null
  ) {
    // GestureRecognizerFactoryWithHandlers是GestureRecognizerFactory子类,所以可以赋值
    gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
      () => TapGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
      (TapGestureRecognizer instance) {
        // TapGestureRecognizer持有了外部传入的回调方法实例,例如*onTapDown*方法
        instance
          ..onTapDown = onTapDown
          ..onTapUp = onTapUp
          ..onTap = onTap
          ..onTapCancel = onTapCancel
          ..onSecondaryTap = onSecondaryTap
          ..onSecondaryTapDown = onSecondaryTapDown
          ..onSecondaryTapUp = onSecondaryTapUp
          ..onSecondaryTapCancel = onSecondaryTapCancel
          ..onTertiaryTapDown = onTertiaryTapDown
          ..onTertiaryTapUp = onTertiaryTapUp
          ..onTertiaryTapCancel = onTertiaryTapCancel
          ..gestureSettings = gestureSettings
          ..supportedDevices = supportedDevices;
      },
    );
  }
  ...
  // 返回RawGestureDetector组件,gestures作为参数传入
  return RawGestureDetector(
    gestures: gestures,
    behavior: behavior,
    excludeFromSemantics: excludeFromSemantics,
    child: child,
  );
}

RawGestureDetector是一个StatefulWidget,它创建了RawGestureDetectorState,看下该StateinitState方法。

可以发现,在initState方法中执行了 _syncAll 方法,传入了GestureDetectorbuild方法中创建的gestures集合。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class RawGestureDetectorState extends State<RawGestureDetector> {
  // 定义手势识别器Map集合
  Map<Type, GestureRecognizer>? _recognizers = const <Type, GestureRecognizer>{};
  SemanticsGestureDelegate? _semantics;

  @override
  void initState() {
    super.initState();
    _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
    _syncAll(widget.gestures);
  }
}

看下RawGestureDetectorState_syncAll 方法,它会将gestures转换为手势识别器集合 _recognizers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
  assert(_recognizers != null);
  // 将_recognizers赋值给局部变量oldRecognizers
  final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
  // 创建一个新的_recognizers
  _recognizers = <Type, GestureRecognizer>{};
  // 遍历gestures的keys,这里的type就是GestureRecognizer了
  for (final Type type in gestures.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 (final Type type in oldRecognizers.keys) {
    if (!_recognizers!.containsKey(type)) {
      oldRecognizers[type]!.dispose();
    }
  }
}

看下该RawGestureDetectorStatebuild方法,可以发现GestureDetector的内部其实是对Listener进行了 _GestureSemantics 一层包装。

我们知道,当事件分发时,就会遍历HitTestResult,然后调用每一个节点(RenderObject)的handleEvent方法。在前面的GestureDetector示例中,此时HitTestResult_path 的情况如下。

对于GestureDetector示例中的内部GestureDetector,它内部的Listener作为第二个HitTestTarget被执行了handleEvent方法,最终会执行 _handlePointerDown 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@override
Widget build(BuildContext context) {
  Widget result = Listener(
    // 执行_handlePointerDown方法
    onPointerDown: _handlePointerDown,
    onPointerPanZoomStart: _handlePointerPanZoomStart,
    behavior: widget.behavior ?? _defaultBehavior,
    child: widget.child,
  );
  if (!widget.excludeFromSemantics) {
    result = _GestureSemantics(
      behavior: widget.behavior ?? _defaultBehavior,
      assignSemantics: _updateSemanticsForRenderObject,
      child: result,
    );
  }
  return result;
}

4.2、RawGestureDetectorState_handlePointerDown 方法

看下RawGestureDetectorState_handlePointerDown 方法,可以发现它对 _recognizers 进行了遍历,执行所有GestureRecognizeraddPointer方法,并把PointerDownEvent作为参数传入了addPointer方法。

1
2
3
4
5
6
void _handlePointerDown(PointerDownEvent event) {
  assert(_recognizers != null);
  for (final GestureRecognizer recognizer in _recognizers!.values) {
    recognizer.addPointer(event);
  }
}

看下GestureRecognizeraddPointer方法,执行isPointerAllowed方法进行if语句判断,用来检查该识别器是否允许跟踪指针。

如果if语句判断为真,那么执行addAllowedPointer方法,注册一个已被检查为手势识别器允许的新指针。

否则,执行handleNonAllowedPointer方法,处理此识别器不允许添加的指针,GestureRecognizer子类可以重写此方法并拒绝手势。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void addPointer(PointerDownEvent event) {
  // 保存指针ID和它们来自的设备类型之间的映射
  _pointerToKind[event.pointer] = event.kind;
  // 分析一
  if (isPointerAllowed(event)) {
    // 分析二
    addAllowedPointer(event);
  } else {
    // 分析三
    handleNonAllowedPointer(event);
  }
}

分析一:

前面我们看GestureDetectorbuild方法时就知道,对于onTapDownonTapUponTaponTapCancel这几个回调,它所创建的手势识别器为TapGestureRecognizer

TapGestureRecognizer实现了isPointerAllowed方法,所以看下TapGestureRecognizerisPointerAllowed方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@override
bool isPointerAllowed(PointerDownEvent event) {
  switch (event.buttons) { 
    case kPrimaryButton:
      // 满足kPrimaryButton条件,但是示例中回调均不为null
      if (onTapDown == null &&
          onTap == null &&
          onTapUp == null &&
          onTapCancel == null) {
        return false;
      }
    case kSecondaryButton:
      if (onSecondaryTap == null &&
          onSecondaryTapDown == null &&
          onSecondaryTapUp == null &&
          onSecondaryTapCancel == null) {
        return false;
      }
    case kTertiaryButton:
      if (onTertiaryTapDown == null &&
          onTertiaryTapUp == null &&
          onTertiaryTapCancel == null) {
        return false;
      }
    default:
      return false;
  }
  // 因此,执行了父类的isPointerAllowed方法
  return super.isPointerAllowed(event);
}

TapGestureRecognizerisPointerAllowed方法也是执行了父类的isPointerAllowed方法,看下GestureRecognizerisPointerAllowed方法。

因为supportedDevices是通过GestureDetector的构造方法传入,而我们没有传入,所以supportedDevicesnull,那么isPointerAllowed方法返回true,因此,会继续执行分析二。

1
2
3
4
5
6
7
8
Set<PointerDeviceKind>? supportedDevices;

@protected
bool isPointerAllowed(PointerDownEvent event) {
  return (supportedDevices == null ||
          supportedDevices!.contains(event.kind)) &&
      _allowedButtonsFilter(event.buttons);
}

分析二:

addAllowedPointer方法在GestureRecognizer是空实现,该方法由GestureRecognizer的子类实现,那么去看下TapGestureRecognizeraddAllowedPointer方法实现。

但是发现TapGestureRecognizer并没有重写该方法,那么看下它的父类BaseTapGestureRecognizeraddAllowedPointer方法实现。

可以看到,在BaseTapGestureRecognizeraddAllowedPointer方法中,最后又执行了父类的addAllowedPointer方法,也就是执行了PrimaryPointerGestureRecognizeraddAllowedPointer方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@override
void addAllowedPointer(PointerDownEvent event) {
  // 此时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);
  }
}

看下PrimaryPointerGestureRecognizeraddAllowedPointer方法,首先执行了父类OneSequenceGestureRecognizeraddAllowedPointer方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@override
void addAllowedPointer(PointerDownEvent event) {
  // 执行父类的addAllowedPointer方法
  super.addAllowedPointer(event);
  if (state == GestureRecognizerState.ready) {
    // 修改_state位possible,表示到目前为止看到的指针事件序列与识别器尝试识别的手势一致,但该手势尚未被明确接受
    _state = GestureRecognizerState.possible;
    // 该识别器正在跟踪的主指针的ID
    // 如果此识别器不再跟踪任何指针,则此字段保存此识别器最近跟踪的主指针的ID。
    // 这使得识别器能够在调用acceptGesture或rejectGesture时知道它最近跟踪哪个指针
    _primaryPointer = event.pointer;
    // 主指针接触屏幕的位置,仅当该识别器正在跟踪至少一个指针时,该值才为非空
    _initialPosition = OffsetPair(local: event.localPosition, global: event.position);
    // deadline在BaseTapGestureRecognizer构造方法中调用父类的构造方法时传入了
    // 对于轻击屏幕,它的值是固定的,为kPressTimeout,等于Duration(milliseconds: 100),此时它表示如果怀疑该手势是轻击,则轻击手势发送 onTapDown 之前必须经过的时间
    // 对于长按屏幕,它的值不是固定的,可以自定义,不传入时默认值为kLongPressTimeout,等于Duration(milliseconds: 500),此时它表示长按手势尝试获胜之前的时间。
    // 当然我们示例中肯定是轻击屏幕,所以这里会执行
    if (deadline != null) {
      // 如果非空,则识别器将在开始跟踪主指针后经过此时间量后调用didExceedDeadline 。
      // 如果主指针在截止deadline之前被接受、拒绝,或者所有指针都已启动或取消,则不会调用didExceedDeadline 
      // 我们示例中,如果轻击超时了,就会执行didExceedDeadlineWithEvent方法,
      // 在didExceedDeadlineWithEvent方法中,最终会执行handleTapDown方法回调给外部GestureDetector,也就是点击超时时也会触发onTapDown回调。
      // didExceedDeadlineWithEvent源码这里不讲解了,可自行查看
      _timer = Timer(deadline!, () => didExceedDeadlineWithEvent(event));
    }
  }
}

看下OneSequenceGestureRecognizeraddAllowedPointer方法,执行了startTrackingPointer方法。

1
2
3
4
5
@override
@protected
void addAllowedPointer(PointerDownEvent event) {
  startTrackingPointer(event.pointer, event.transform);
}

此处startTrackingPointer方法由OneSequenceGestureRecognizer的子类BaseTapGestureRecognizer去实现,看下BaseTapGestureRecognizerstartTrackingPointer方法。

可以看到,在BaseTapGestureRecognizerstartTrackingPointer方法中,又执行了父类OneSequenceGestureRecognizerstartTrackingPointer方法。

1
2
3
4
5
6
7
8
9
@override
@protected
void startTrackingPointer(int pointer, [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);
}

看下OneSequenceGestureRecognizerstartTrackingPointer方法,执行了GestureBinding中成员变量pointerRouteraddRoute方法,将路由添加到路由表。

然后执行 _addPointerToArena 方法,将新成员(例如手势识别器)添加到竞技场。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{};
final Set<int> _trackedPointers = HashSet<int>();

// 导致与给定指针 D相关的事件被路由到此识别器。
// 指针事件根据transform进行转换,然后传递给handleEvent。transform参数的值通常从PointerDownEvent.transform获取,以将事件从全局坐标空间转换到事件接收者的坐标空间。如果不需要转换,它可能为空。
// 使用stopTrackingPointer删除此函数添加的路由。
// 此方法还会将此识别器(或其team ,如果它非空)添加到指定指针的手势区域。
// 这是由OneSequenceGestureRecognizer.addAllowedPointer调用的。
@protected
void startTrackingPointer(int pointer, [Matrix4? transform]) {
  // 将路由添加到路由表
  GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);
  // 将指针添加到追踪指针Set集合
  _trackedPointers.add(pointer);
  // TODO(goderbauer): Enable assert after recognizers properly clean up their defunct `_entries`, see https://github.com/flutter/flutter/issues/117356.
  // assert(!_entries.containsKey(pointer));
  // 将新成员(例如手势识别器)添加到竞技场
  // 给定的GestureArenaMember(手势识别器)可以在多个具有不同指针id的区域中拥有多个条目。
  // 将返回的GestureArenaEntry存入Map集合中
  _entries[pointer] = _addPointerToArena(pointer);
}

看下PointerRouteraddRoute方法,可以发现pointer与路由表routes相关联,并且路由表的条目让OneSequenceGestureRecognizerhandleEvent方法与PointerDownEvent中的transform进行了关联。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// _routeMap以pointer作为Key,Map<PointerRoute, Matrix4?>作为Value
final Map<int, Map<PointerRoute, Matrix4?>> _routeMap = <int, Map<PointerRoute, Matrix4?>>{};

void addRoute(int pointer, PointerRoute route, [Matrix4? transform]) {
  // 如果_routeMap中存在该值为pointer的Key,那么返回它的Value,也就是一个Map
  // 否则,以该pointer为Key,空的Map为Value,创建一个新的Key-Value对存入_routeMap中,最后返回它的Value,也就是一个空的Map
  final Map<PointerRoute, Matrix4?> routes = _routeMap.putIfAbsent(
    pointer,
    () => <PointerRoute, Matrix4?>{},
  );
  assert(!routes.containsKey(route));
  // 再把PointerDownEvent中的transform存入该routes中,Key为route,也就是handleEvent方法
  // 这里已经将OneSequenceGestureRecognizer的handleEvent方法与PointerDownEvent中的transform进行了关联,不过handleEvent方法由OneSequenceGestureRecognizer的子类实现
  routes[route] = transform;
}

再看下OneSequenceGestureRecognizer_addPointerToArena 方法,执行了GestureBinding中成员变量gestureArenaadd方法,将新成员(例如手势识别器)添加到竞技场。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
GestureArenaEntry _addPointerToArena(int pointer) {
  // 该识别器所属的团队(如果有)。
  // 如果team为null,则此识别器直接在GestureArenaManager中竞争,将一系列指针事件识别为手势。如果team不为空,则该识别器在竞技场中与同一团队的其它识别器一起竞争。
  // 仅当团队未参加竞技场时,才能将识别器分配给该团队。例如,将识别器分配给团队的常见时间是在创建识别器后不久。
  if (_team != null) {
    return _team!.add(pointer, this);
  }
  // 将新成员(例如手势识别器)添加到竞技场
  return GestureBinding.instance.gestureArena.add(pointer, this);
}

看下GestureArenaManageradd方法,可以发现pointer与手势竞技场 _GestureArena 相关联。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// _arenas以pointer作为Key,_GestureArena作为Value
final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};

/// Adds a new member (e.g., gesture recognizer) to the arena.
GestureArenaEntry add(int pointer, GestureArenaMember member) {
  // 如果_arenas中存在该值为pointer的Key,那么返回它的Value,也就是一个_GestureArena
  // 否则,以该pointer为Key,新的_GestureArena为Value,创建一个新的Key-Value对存入_arenas中,最后返回它的Value,也就是一个新的_GestureArena
  final _GestureArena state = _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对象
  return GestureArenaEntry._(this, pointer, member);
}

分析三:

回到GestureRecognizeraddPointer方法,看下handleNonAllowedPointer方法,它用来处理此识别器不允许添加的指针,子类可以重写此方法并拒绝手势。

一般情况下,handleNonAllowedPointer方法不会执行。对于TapGestureRecognizer来说,如果一个回调(例如onTapDown)都不传入GestureDetector,或者传入的supportedDevices和系统识别出来的输入设备的类型不一致,那么isPointerAllowed方法就会返回false,所以会执行handleNonAllowedPointer方法。

可以发现,在GestureRecognizerhandleNonAllowedPointer方法是一个空实现,由GestureRecognizer的子类去实现,那么看下PrimaryPointerGestureRecognizerhandleNonAllowedPointer方法,最后执行了父类的handleNonAllowedPointer方法。

1
2
3
4
5
6
7
@override
void handleNonAllowedPointer(PointerDownEvent event) {
  // 该指针是通过赢得竞技场而被接受还是由调用acceptGesture的子类定义。
  if (!_gestureAccepted) {
    super.handleNonAllowedPointer(event);
  }
}

看下OneSequenceGestureRecognizerhandleNonAllowedPointer方法,执行了resolve方法,传入的状态是GestureDisposition.rejected

resolve方法也是可以被重写的,它由子类BaseTapGestureRecognizer去实现。

1
2
3
4
5
6
7
@override
@protected
void handleNonAllowedPointer(PointerDownEvent event) {
  // 使用给定的配置解决此识别器参与每个手势领域的问题
  // 这里传入了GestureDisposition.rejected,说明该手势被拒绝作为对用户输入的解释
  resolve(GestureDisposition.rejected);
}

看下BaseTapGestureRecognizerresolve方法,最后执行了父类的resolve方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@override
void resolve(GestureDisposition disposition) {
  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);
}

看下OneSequenceGestureRecognizerresolve方法,这里disposition传入的是GestureDisposition.rejected,执行所有GestureArenaEntryresolve方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@protected
@mustCallSuper
void resolve(GestureDisposition disposition) {
  // 当然了,如果之前没调用startTrackingPointer方法的话,_entries就为空,那么localEntries也为空
  final List<GestureArenaEntry> localEntries = List<GestureArenaEntry>.of(_entries.values);
  _entries.clear();
  // 如果localEntries为空,此处不会执行
  for (final GestureArenaEntry entry in localEntries) {
    entry.resolve(disposition);
  }
}

看下GestureArenaEntryresolve方法,可以看到,执行了GestureArenaManager_resolve 方法,该方法表示拒绝或接受手势识别器。

前面一路传过来的dispositionGestureDisposition.rejected,说明要拒绝手势识别器。

1
2
3
4
5
final GestureArenaManager _arena;

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

4.3、GestureBindinghandleEvent方法

之前讲过,WidgetsFlutterBinding也命中测试了,由于它是最后被添加到HitTestResult中的,所以在事件分发阶段WidgetsFlutterBindinghandleEvent会在最后被调用,只是WidgetsFlutterBinding没有重写handleEvent方法,这里是调用了混入GestureBindinghandleEvent方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
  // 分析一
  pointerRouter.route(event);
  if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
    // 分析二
    gestureArena.close(event.pointer);
  } else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
    // 分析三
    gestureArena.sweep(event.pointer);
  } else if (event is PointerSignalEvent) {
    pointerSignalResolver.resolve(event);
  }
}

4.3.1、PointerDownEvent事件分发

分析一:

PointerRouterroute方法中,执行了 _dispatchEventToRoutes 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void route(PointerEvent event) {
  // 取出每个pointer对应的路由表,每个路由表都关联了所有手势识别器的handleEvent方法
  final Map<PointerRoute, Matrix4?>? routes = _routeMap[event.pointer];
  // 拷贝一份_globalRoutes,这个是全局路由相关的,本示例这里不涉及
  final Map<PointerRoute, Matrix4?> copiedGlobalRoutes = Map<PointerRoute, Matrix4?>.of(_globalRoutes);
  if (routes != null) {
    // 执行handleEvent方法
    _dispatchEventToRoutes(
      event,
      routes,
      Map<PointerRoute, Matrix4?>.of(routes),
    );
  }
  // 这个是全局路由相关的,本示例这里不涉及
  _dispatchEventToRoutes(event, _globalRoutes, copiedGlobalRoutes);
}

看下PointerRouter_dispatchEventToRoutes 方法,遍历了路由表,执行 _dispatch 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void _dispatchEventToRoutes(
  PointerEvent event,
  Map<PointerRoute, Matrix4?> referenceRoutes,
  Map<PointerRoute, Matrix4?> copiedRoutes,
) {
  copiedRoutes.forEach((PointerRoute route, Matrix4? transform) {
    if (referenceRoutes.containsKey(route)) {
      _dispatch(event, route, transform);
    }
  });
}

看下PointerRouter_dispatch 方法,可以发现,执行了route方法,此处PointerRoute其实是OneSequenceGestureRecognizerstartTrackingPointer方法中通过调用GestureBinding的成员变量pointerRouteraddRoute方法传入的handleEvent方法。

不过OneSequenceGestureRecognizerhandleEvent方法是一个空实现,它由子类PrimaryPointerGestureRecognizer来实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@pragma('vm:notify-debugger-on-exception')
void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) {
  try {
    event = event.transformed(transform);
    // 执行手势识别器的handleEvent方法
    route(event);
  } catch (exception, stack) {
    InformationCollector? collector;
    assert(() {
      collector = () => <DiagnosticsNode>[
        DiagnosticsProperty<PointerRouter>('router', this, level: DiagnosticLevel.debug),
        DiagnosticsProperty<PointerRoute>('route', route, level: DiagnosticLevel.debug),
        DiagnosticsProperty<PointerEvent>('event', event, level: DiagnosticLevel.debug),
      ];
      return true;
    }());
    FlutterError.reportError(FlutterErrorDetails(
      exception: exception,
      stack: stack,
      library: 'gesture library',
      context: ErrorDescription('while routing a pointer event'),
      informationCollector: collector,
    ));
  }
}

看下PrimaryPointerGestureRecognizerhandleEvent方法,可以发现执行了handlePrimaryPointer方法,该方法由子类BaseTapGestureRecognizer去实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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 {
      // 因为先是分发PointerDownEvent,所以执行这里
      handlePrimaryPointer(event);
    }
  }
  stopTrackingIfPointerNoLongerDown(event);
}

看下BaseTapGestureRecognizerhandlePrimaryPointer方法,可以看到,该方法内部并没有对PointerDownEvent事件进行处理,所以整个 pointerRouter.route(event) 相当于没执行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@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) { // 此处buttons相等,不满足条件
    resolve(GestureDisposition.rejected);
    stopTrackingPointer(primaryPointer!);
  }
}

分析二:

如果eventPointerDownEvent,那么就会执行GestureArenaManagerclose方法,在该方法中执行了 _tryToResolveArena 方法。

1
2
3
4
5
6
7
8
9
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);
}

看下GestureArenaManager_tryToResolveArena 方法,执行了 _resolveByDefault 方法,在该方法中,此时state.members的数量为2,也就是两个TapGestureRecognizer手势识别器,并且此时还没有获胜者,所以state.eagerWinnernull,整个 _tryToResolveArena 方法相当于没执行。

那么,*gestureArena.close(event.pointer)*也相当于没执行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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!);
  }
}

4.3.2、PointerUpEvent事件分发

分析一:

PointerRouterroute方法中,执行了 _dispatchEventToRoutes 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void route(PointerEvent event) {
  // 取出每个pointer对应的路由表,每个路由表都关联了所有手势识别器的handleEvent方法
  final Map<PointerRoute, Matrix4?>? routes = _routeMap[event.pointer];
  // 拷贝一份_globalRoutes,这个是全局路由相关的,本示例这里不涉及
  final Map<PointerRoute, Matrix4?> copiedGlobalRoutes = Map<PointerRoute, Matrix4?>.of(_globalRoutes);
  if (routes != null) {
    // 执行handleEvent方法
    _dispatchEventToRoutes(
      event,
      routes,
      Map<PointerRoute, Matrix4?>.of(routes),
    );
  }
  // 这个是全局路由相关的,本示例这里不涉及
  _dispatchEventToRoutes(event, _globalRoutes, copiedGlobalRoutes);
}

看下PointerRouter_dispatchEventToRoutes 方法,遍历了路由表,执行 _dispatch 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void _dispatchEventToRoutes(
  PointerEvent event,
  Map<PointerRoute, Matrix4?> referenceRoutes,
  Map<PointerRoute, Matrix4?> copiedRoutes,
) {
  copiedRoutes.forEach((PointerRoute route, Matrix4? transform) {
    if (referenceRoutes.containsKey(route)) {
      _dispatch(event, route, transform);
    }
  });
}

看下PointerRouter_dispatch 方法,可以发现,执行了route方法,此处PointerRoute其实是OneSequenceGestureRecognizerstartTrackingPointer方法中通过调用GestureBinding的成员变量pointerRouteraddRoute方法传入的handleEvent方法。

不过OneSequenceGestureRecognizerhandleEvent方法是一个空实现,它由子类PrimaryPointerGestureRecognizer来实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@pragma('vm:notify-debugger-on-exception')
void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) {
  try {
    event = event.transformed(transform);
    route(event);
  } catch (exception, stack) {
    InformationCollector? collector;
    assert(() {
      collector = () => <DiagnosticsNode>[
        DiagnosticsProperty<PointerRouter>('router', this, level: DiagnosticLevel.debug),
        DiagnosticsProperty<PointerRoute>('route', route, level: DiagnosticLevel.debug),
        DiagnosticsProperty<PointerEvent>('event', event, level: DiagnosticLevel.debug),
      ];
      return true;
    }());
    FlutterError.reportError(FlutterErrorDetails(
      exception: exception,
      stack: stack,
      library: 'gesture library',
      context: ErrorDescription('while routing a pointer event'),
      informationCollector: collector,
    ));
  }
}

看下PrimaryPointerGestureRecognizerhandleEvent方法,可以发现执行了handlePrimaryPointer方法,该方法由子类BaseTapGestureRecognizer去实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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);
}

看下BaseTapGestureRecognizerhandlePrimaryPointer方法,此时event的类型为PointerUpEvent,所以执行 _checkUp 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@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!);
  }
}

看下BaseTapGestureRecognizer_checkUp 方法,此时是没有获胜者的,所以 _wonArenaForPrimaryPointerfalse,那么 _checkUp 方法就会return,不会继续向下执行,所以整个 pointerRouter.route(event) 相当于没执行。

1
2
3
4
5
6
7
8
void _checkUp() {
  if (!_wonArenaForPrimaryPointer || _up == null) {
    return;
  }
  assert(_up!.pointer == _down!.pointer);
  handleTapUp(down: _down!, up: _up!);
  _reset();
}

分析三:

如果eventPointerUpEvent,那么就会执行GestureArenaManagersweep方法,在该方法中,执行了List中第一个手势识别器的acceptGesture方法,让其它手势识别器执行rejectGesture方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 强制解决竞技场,让第一个成员获胜。
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);
    }
  }
}

看下BaseTapGestureRecognizeracceptGesture方法,可以看到,执行了父类的acceptGesture方法,然后执行了 _checkDown_checkUp 方法。

1
2
3
4
5
6
7
8
9
@override
void acceptGesture(int pointer) {
  super.acceptGesture(pointer);
  if (pointer == primaryPointer) {
    _checkDown();
    _wonArenaForPrimaryPointer = true;
    _checkUp();
  }
}

看下PrimaryPointerGestureRecognizeracceptGesture方法,可以看到,做了一下清理工作。

1
2
3
4
5
6
7
@override
void acceptGesture(int pointer) {
  if (pointer == primaryPointer) {
    _stopTimer();
    _gestureAccepted = true;
  }
}

看下BaseTapGestureRecognizer_checkDown 方法,执行了handleTapDown方法。

1
2
3
4
5
6
7
void _checkDown() {
  if (_sentTapDown) {
    return;
  }
  handleTapDown(down: _down!);
  _sentTapDown = true;
}

看下BaseTapGestureRecognizerhandleTapDown方法,该方法由子类TapGestureRecognizer实现。可以看到,调用了invokeCallback方法去调用外部GestureDetector传入的回调。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@protected
@override
void handleTapDown({required PointerDownEvent down}) {
  final TapDownDetails details = TapDownDetails(
    globalPosition: down.position,
    localPosition: down.localPosition,
    kind: getKindForPointer(down.pointer),
  );
  switch (down.buttons) {
    case kPrimaryButton:
      if (onTapDown != null) {
        invokeCallback<void>('onTapDown', () => onTapDown!(details));
      }
    case kSecondaryButton:
      if (onSecondaryTapDown != null) {
        invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown!(details));
      }
    case kTertiaryButton:
      if (onTertiaryTapDown != null) {
        invokeCallback<void>('onTertiaryTapDown', () => onTertiaryTapDown!(details));
      }
    default:
  }
}

看下BaseTapGestureRecognizer_checkUp 方法,执行了handleTapUp方法。

1
2
3
4
5
6
7
8
void _checkUp() {
  if (!_wonArenaForPrimaryPointer || _up == null) {
    return;
  }
  assert(_up!.pointer == _down!.pointer);
  handleTapUp(down: _down!, up: _up!);
  _reset();
}

看下BaseTapGestureRecognizerhandleTapUp方法,该方法由子类TapGestureRecognizer实现。可以看到,调用了invokeCallback方法去调用外部GestureDetector传入的回调。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@protected
@override
void handleTapUp({ required PointerDownEvent down, required PointerUpEvent up}) {
  final TapUpDetails details = TapUpDetails(
    kind: up.kind,
    globalPosition: up.position,
    localPosition: up.localPosition,
  );
  switch (down.buttons) {
    case kPrimaryButton:
      if (onTapUp != null) {
        invokeCallback<void>('onTapUp', () => onTapUp!(details));
      }
      if (onTap != null) {
        invokeCallback<void>('onTap', onTap!);
      }
    case kSecondaryButton:
      if (onSecondaryTapUp != null) {
        invokeCallback<void>('onSecondaryTapUp', () => onSecondaryTapUp!(details));
      }
      if (onSecondaryTap != null) {
        invokeCallback<void>('onSecondaryTap', () => onSecondaryTap!());
      }
    case kTertiaryButton:
      if (onTertiaryTapUp != null) {
        invokeCallback<void>('onTertiaryTapUp', () => onTertiaryTapUp!(details));
      }
    default:
  }
}

五、同一个GestureDetector多种手势之间的冲突

前面所讲的GestureDetector示例属于多个GestureDetector之间的手势竞争,由于手势竞争最终只有一个胜出者,所以,当我们通过一个GestureDetector监听多种手势时,也可能会产生冲突。例如下面这个例子,同时监听轻击事件与双击事件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: const ColoredBox(color: Colors.blueAccent),
      onTap: () => debugPrint('onTap'),
      onTapDown: (TapDownDetails details) => debugPrint('onTapDown'),
      onTapUp: (TapUpDetails details) => debugPrint('onTapUp'),
      onTapCancel: () => debugPrint('onTapCancel'),
      onDoubleTapDown: (TapDownDetails details) => debugPrint('onDoubleTapDown'),
    );
  }
}

程序运行起来后双击界面,日志打印如下:

1
I/flutter ( 8036): onDoubleTapDown

可以发现没有打印Tap相关的日志,因为双击包含两次间隔时间很短连续轻点击,第一次点击时执行了 _registerFirstTap 方法,开启间隔时间计时(这个值为kDoubleTapTimeout,相较于第一次轻点击会有200ms的延迟),并且通过执行GestureArenaManagerhold方法来防止竞技场被扫荡。通常,在所有其它PointerUpEvent处理完成后,会在竞技场中通过sweep选择获胜者。

相当于不给第一次轻点击获胜的机会,然后在没超过间隔时间内再次轻点击时,就完成了双击,此时双击手势已有完整的语义。接着就会调用 _registerSecondTap 方法宣称获得胜利。

这点通过Flutter内部手势竞技场的日志打印也可以看出来:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
I/flutter ( 8794): Gesture arena 1    ❙ ★ Opening new gesture arena.
I/flutter ( 8794): Gesture arena 1    ❙ Adding: TapGestureRecognizer#15e11(debugOwner: GestureDetector, state: ready, button: 1)
I/flutter ( 8794): Gesture arena 1    ❙ Adding: DoubleTapGestureRecognizer#c0988(debugOwner: GestureDetector)
I/flutter ( 8794): Gesture arena 1    ❙ Closing with 2 members.
I/flutter ( 8794): Gesture arena 1    ❙ Holding with 2 members.
I/flutter ( 8794): Gesture arena 1    ❙ Delaying sweep with 2 members.
I/flutter ( 8794): Gesture arena 2    ❙ ★ Opening new gesture arena.
I/flutter ( 8794): Gesture arena 2    ❙ Adding: TapGestureRecognizer#15e11(debugOwner: GestureDetector, state: ready, button: 1)
I/flutter ( 8794): onDoubleTapDown
I/flutter ( 8794): Gesture arena 2    ❙ Adding: DoubleTapGestureRecognizer#c0988(debugOwner: GestureDetector)
I/flutter ( 8794): Gesture arena 2    ❙ Closing with 2 members.
I/flutter ( 8794): Gesture arena 1    ❙ Accepting: DoubleTapGestureRecognizer#c0988(debugOwner: GestureDetector)
I/flutter ( 8794): Gesture arena 1    ❙ Self-declared winner: DoubleTapGestureRecognizer#c0988(debugOwner: GestureDetector)
I/flutter ( 8794): Gesture arena 2    ❙ Accepting: DoubleTapGestureRecognizer#c0988(debugOwner: GestureDetector)
I/flutter ( 8794): Gesture arena 2    ❙ Self-declared winner: DoubleTapGestureRecognizer#c0988(debugOwner: GestureDetector)

六、解决手势冲突

手势是对原始指针的语义化的识别,手势冲突只是手势级别的,也就是说只会在组件树中的多个 GestureDetector之间才有冲突的场景,如果压根就没有使用GestureDetector则不存在所谓的冲突,因为每一个节点都能收到事件,只是在GestureDetector中为了识别语义,它会去决定哪些子节点应该忽略事件,哪些节点应该生效。

解决手势冲突的方法有两种:

1、使用Listener。这相当于跳出了手势识别那套规则。

2、通过自定义手势识别器(Recognizer)。

6.1、通过Listener解决手势冲突

通过Listener解决手势冲突的原因是竞争只是针对手势的,而Listener是监听原始指针事件,原始指针事件并非语义话的手势,所以根本不会走手势竞争的逻辑,所以也就不会相互影响。拿上面两个GestureDetector嵌套的例子来说,通过Listener的解决方式为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Listener(
      child: GestureDetector(
        child: const ColoredBox(color: Colors.blueAccent),
        onTap: () => debugPrint('inner onTap'),
        onTapDown: (TapDownDetails details) => debugPrint('inner onTapDown'),
        onTapUp: (TapUpDetails details) => debugPrint('inner onTapUp'),
        onTapCancel: () => debugPrint('inner onTapCancel'),
      ),
      onPointerDown: (PointerDownEvent event) => debugPrint('outer onPointerDown'),
      onPointerUp: (PointerUpEvent event) => debugPrint('outer onPointerUp'),
      onPointerCancel: (PointerCancelEvent event) => debugPrint('outer onPointerCancel'),
    );
  }
}

代码很简单,只需将GestureDetector换位Listener即可,可以两个都换,也可以只换一个。可以看见,通过Listener直接识别原始指针事件来解决冲突的方法很简单,因此,当遇到手势冲突时,我们应该优先考虑Listener

6.2、通过自定义手势识别器Recognizer解决手势冲突

自定义手势识别器的方式比较麻烦,原理是当确定手势竞争胜出者时,会调用胜出者的acceptGesture方法,表示“宣布成功”,然后会调用其它手势识别其的rejectGesture方法,表示“宣布失败”。

既然如此,我们可以自定义手势识别器(Recognizer),然后去重写它的rejectGesture方法:在里面调用acceptGesture方法,这就相当于它失败是强制将它也变成竞争的成功者了,这样它的回调也就会执行。

我们先自定义tap手势识别器(Recognizer):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return customGestureDetector(
      child: GestureDetector(
        child: const ColoredBox(color: Colors.blueAccent),
        onTap: () => debugPrint('inner onTap'),
        onTapDown: (TapDownDetails details) => debugPrint('inner onTapDown'),
        onTapUp: (TapUpDetails details) => debugPrint('inner onTapUp'),
        onTapCancel: () => debugPrint('inner onTapCancel'),
      ),
      onTap: () => debugPrint('outer onTap'),
      onTapDown: (TapDownDetails details) => debugPrint('outer onTapDown'),
      onTapUp: (TapUpDetails details) => debugPrint('outer onTapUp'),
      onTapCancel: () => debugPrint('outer onTapCancel'),
    );
  }
}

class CustomTapGestureRecognizer extends TapGestureRecognizer {
  @override
  void rejectGesture(int pointer) {
    // 宣布成功
    acceptGesture(pointer);
    // NOTE:这里保留了后续清理操作,只是不知有无隐患
    super.rejectGesture(pointer);
  }
}

/// 创建一个新的GestureDetector,用我们自定义的TapGestureRecognizer替换默认的
RawGestureDetector customGestureDetector({
  GestureTapCallback? onTap,
  GestureTapDownCallback? onTapDown,
  GestureTapUpCallback? onTapUp,
  GestureTapCancelCallback? onTapCancel,
  Widget? child,
}) {
  return RawGestureDetector(
    gestures: {
      CustomTapGestureRecognizer: GestureRecognizerFactoryWithHandlers<CustomTapGestureRecognizer>(
        () => CustomTapGestureRecognizer(),
        (CustomTapGestureRecognizer instance) {
          instance
            ..onTap = onTap
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTapCancel = onTapCancel;
        },
      )
    },
    child: child,
  );
}

程序运行起来,轻点击后日志打印如下:

1
2
3
4
5
6
I/flutter ( 9224): inner onTapDown
I/flutter ( 9224): inner onTapUp
I/flutter ( 9224): inner onTap
I/flutter ( 9224): outer onTapDown
I/flutter ( 9224): outer onTapUp
I/flutter ( 9224): outer onTap

这样就OK了,需要注意,这个例子同时说明了一次手势处理过程也是可以有多个胜出者的。

七、总结

1、每一个手势识别器GestureRecognizer都是一个“竞争者”(GestureArenaMember),当发生指针事件时,它们都要在“竞技场”去竞争本次事件的处理权,默认情况最终只有一个“竞争者”会胜出(win)。

2、GestureRecognizerhandleEvent中会识别手势,如果发生了某个手势,竞争者可以宣布自己是否胜出,一旦有一个竞争者胜出,竞技场管理者(GestureArenaManager)就会通知其他竞争者失败。

3、胜出者的acceptGesture会被调用,其余失败者的rejectGesture将会被调用。