1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'package:flutter/foundation.dart'; |
6 | import 'package:flutter/gestures.dart'; |
7 | import 'package:flutter/rendering.dart'; |
8 | |
9 | import 'basic.dart'; |
10 | import 'framework.dart'; |
11 | import 'media_query.dart'; |
12 | |
13 | export 'package:flutter/gestures.dart' show |
14 | DragDownDetails, |
15 | DragEndDetails, |
16 | DragStartDetails, |
17 | DragUpdateDetails, |
18 | ForcePressDetails, |
19 | GestureDragCancelCallback, |
20 | GestureDragDownCallback, |
21 | GestureDragEndCallback, |
22 | GestureDragStartCallback, |
23 | GestureDragUpdateCallback, |
24 | GestureForcePressEndCallback, |
25 | GestureForcePressPeakCallback, |
26 | GestureForcePressStartCallback, |
27 | GestureForcePressUpdateCallback, |
28 | GestureLongPressCallback, |
29 | GestureLongPressEndCallback, |
30 | GestureLongPressMoveUpdateCallback, |
31 | GestureLongPressStartCallback, |
32 | GestureLongPressUpCallback, |
33 | GestureScaleEndCallback, |
34 | GestureScaleStartCallback, |
35 | GestureScaleUpdateCallback, |
36 | GestureTapCallback, |
37 | GestureTapCancelCallback, |
38 | GestureTapDownCallback, |
39 | GestureTapUpCallback, |
40 | LongPressEndDetails, |
41 | LongPressMoveUpdateDetails, |
42 | LongPressStartDetails, |
43 | ScaleEndDetails, |
44 | ScaleStartDetails, |
45 | ScaleUpdateDetails, |
46 | TapDownDetails, |
47 | TapUpDetails, |
48 | Velocity; |
49 | export 'package:flutter/rendering.dart' show RenderSemanticsGestureHandler; |
50 | |
51 | // Examples can assume: |
52 | // late bool _lights; |
53 | // void setState(VoidCallback fn) { } |
54 | // late String _last; |
55 | // late Color _color; |
56 | |
57 | /// Factory for creating gesture recognizers. |
58 | /// |
59 | /// `T` is the type of gesture recognizer this class manages. |
60 | /// |
61 | /// Used by [RawGestureDetector.gestures]. |
62 | @optionalTypeArgs |
63 | abstract class GestureRecognizerFactory<T extends GestureRecognizer> { |
64 | /// Abstract const constructor. This constructor enables subclasses to provide |
65 | /// const constructors so that they can be used in const expressions. |
66 | const GestureRecognizerFactory(); |
67 | |
68 | /// Must return an instance of T. |
69 | T constructor(); |
70 | |
71 | /// Must configure the given instance (which will have been created by |
72 | /// `constructor`). |
73 | /// |
74 | /// This normally means setting the callbacks. |
75 | void initializer(T instance); |
76 | |
77 | bool _debugAssertTypeMatches(Type type) { |
78 | assert(type == T, 'GestureRecognizerFactory of type $T was used where type $type was specified.' ); |
79 | return true; |
80 | } |
81 | } |
82 | |
83 | /// Signature for closures that implement [GestureRecognizerFactory.constructor]. |
84 | typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function(); |
85 | |
86 | /// Signature for closures that implement [GestureRecognizerFactory.initializer]. |
87 | typedef GestureRecognizerFactoryInitializer<T extends GestureRecognizer> = void Function(T instance); |
88 | |
89 | /// Factory for creating gesture recognizers that delegates to callbacks. |
90 | /// |
91 | /// Used by [RawGestureDetector.gestures]. |
92 | class GestureRecognizerFactoryWithHandlers<T extends GestureRecognizer> extends GestureRecognizerFactory<T> { |
93 | /// Creates a gesture recognizer factory with the given callbacks. |
94 | const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer); |
95 | |
96 | final GestureRecognizerFactoryConstructor<T> _constructor; |
97 | |
98 | final GestureRecognizerFactoryInitializer<T> _initializer; |
99 | |
100 | @override |
101 | T constructor() => _constructor(); |
102 | |
103 | @override |
104 | void initializer(T instance) => _initializer(instance); |
105 | } |
106 | |
107 | /// A widget that detects gestures. |
108 | /// |
109 | /// Attempts to recognize gestures that correspond to its non-null callbacks. |
110 | /// |
111 | /// If this widget has a child, it defers to that child for its sizing behavior. |
112 | /// If it does not have a child, it grows to fit the parent instead. |
113 | /// |
114 | /// {@youtube 560 315 https://www.youtube.com/watch?v=WhVXkCFPmK4} |
115 | /// |
116 | /// By default a GestureDetector with an invisible child ignores touches; |
117 | /// this behavior can be controlled with [behavior]. |
118 | /// |
119 | /// GestureDetector also listens for accessibility events and maps |
120 | /// them to the callbacks. To ignore accessibility events, set |
121 | /// [excludeFromSemantics] to true. |
122 | /// |
123 | /// See <http://flutter.dev/gestures/> for additional information. |
124 | /// |
125 | /// Material design applications typically react to touches with ink splash |
126 | /// effects. The [InkWell] class implements this effect and can be used in place |
127 | /// of a [GestureDetector] for handling taps. |
128 | /// |
129 | /// {@tool dartpad} |
130 | /// This example contains a black light bulb wrapped in a [GestureDetector]. It |
131 | /// turns the light bulb yellow when the "TURN LIGHT ON" button is tapped by |
132 | /// setting the `_lights` field, and off again when "TURN LIGHT OFF" is tapped. |
133 | /// |
134 | /// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.0.dart ** |
135 | /// {@end-tool} |
136 | /// |
137 | /// {@tool dartpad} |
138 | /// This example uses a [Container] that wraps a [GestureDetector] widget which |
139 | /// detects a tap. |
140 | /// |
141 | /// Since the [GestureDetector] does not have a child, it takes on the size of its |
142 | /// parent, making the entire area of the surrounding [Container] clickable. When |
143 | /// tapped, the [Container] turns yellow by setting the `_color` field. When |
144 | /// tapped again, it goes back to white. |
145 | /// |
146 | /// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.1.dart ** |
147 | /// {@end-tool} |
148 | /// |
149 | /// ### Troubleshooting |
150 | /// |
151 | /// Why isn't my parent [GestureDetector.onTap] method called? |
152 | /// |
153 | /// Given a parent [GestureDetector] with an onTap callback, and a child |
154 | /// GestureDetector that also defines an onTap callback, when the inner |
155 | /// GestureDetector is tapped, both GestureDetectors send a [GestureRecognizer] |
156 | /// into the gesture arena. This is because the pointer coordinates are within the |
157 | /// bounds of both GestureDetectors. The child GestureDetector wins in this |
158 | /// scenario because it was the first to enter the arena, resolving as first come, |
159 | /// first served. The child onTap is called, and the parent's is not as the gesture has |
160 | /// been consumed. |
161 | /// For more information on gesture disambiguation see: |
162 | /// [Gesture disambiguation](https://docs.flutter.dev/development/ui/advanced/gestures#gesture-disambiguation). |
163 | /// |
164 | /// Setting [GestureDetector.behavior] to [HitTestBehavior.opaque] |
165 | /// or [HitTestBehavior.translucent] has no impact on parent-child relationships: |
166 | /// both GestureDetectors send a GestureRecognizer into the gesture arena, only one wins. |
167 | /// |
168 | /// Some callbacks (e.g. onTapDown) can fire before a recognizer wins the arena, |
169 | /// and others (e.g. onTapCancel) fire even when it loses the arena. Therefore, |
170 | /// the parent detector in the example above may call some of its callbacks even |
171 | /// though it loses in the arena. |
172 | /// |
173 | /// {@tool dartpad} |
174 | /// This example uses a [GestureDetector] that wraps a green [Container] and a second |
175 | /// GestureDetector that wraps a yellow Container. The second GestureDetector is |
176 | /// a child of the green Container. |
177 | /// Both GestureDetectors define an onTap callback. When the callback is called it |
178 | /// adds a red border to the corresponding Container. |
179 | /// |
180 | /// When the green Container is tapped, it's parent GestureDetector enters |
181 | /// the gesture arena. It wins because there is no competing GestureDetector and |
182 | /// the green Container shows a red border. |
183 | /// When the yellow Container is tapped, it's parent GestureDetector enters |
184 | /// the gesture arena. The GestureDetector that wraps the green Container also |
185 | /// enters the gesture arena (the pointer events coordinates are inside both |
186 | /// GestureDetectors bounds). The GestureDetector that wraps the yellow Container |
187 | /// wins because it was the first detector to enter the arena. |
188 | /// |
189 | /// This example sets [debugPrintGestureArenaDiagnostics] to true. |
190 | /// This flag prints useful information about gesture arenas. |
191 | /// |
192 | /// Changing the [GestureDetector.behavior] property to [HitTestBehavior.translucent] |
193 | /// or [HitTestBehavior.opaque] has no impact: both GestureDetectors send a [GestureRecognizer] |
194 | /// into the gesture arena, only one wins. |
195 | /// |
196 | /// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.2.dart ** |
197 | /// {@end-tool} |
198 | /// |
199 | /// ## Debugging |
200 | /// |
201 | /// To see how large the hit test box of a [GestureDetector] is for debugging |
202 | /// purposes, set [debugPaintPointersEnabled] to true. |
203 | /// |
204 | /// See also: |
205 | /// |
206 | /// * [Listener], a widget for listening to lower-level raw pointer events. |
207 | /// * [MouseRegion], a widget that tracks the movement of mice, even when no |
208 | /// button is pressed. |
209 | /// * [RawGestureDetector], a widget that is used to detect custom gestures. |
210 | class GestureDetector extends StatelessWidget { |
211 | /// Creates a widget that detects gestures. |
212 | /// |
213 | /// Pan and scale callbacks cannot be used simultaneously because scale is a |
214 | /// superset of pan. Use the scale callbacks instead. |
215 | /// |
216 | /// Horizontal and vertical drag callbacks cannot be used simultaneously |
217 | /// because a combination of a horizontal and vertical drag is a pan. |
218 | /// Use the pan callbacks instead. |
219 | /// |
220 | /// {@youtube 560 315 https://www.youtube.com/watch?v=WhVXkCFPmK4} |
221 | /// |
222 | /// By default, gesture detectors contribute semantic information to the tree |
223 | /// that is used by assistive technology. |
224 | GestureDetector({ |
225 | super.key, |
226 | this.child, |
227 | this.onTapDown, |
228 | this.onTapUp, |
229 | this.onTap, |
230 | this.onTapCancel, |
231 | this.onSecondaryTap, |
232 | this.onSecondaryTapDown, |
233 | this.onSecondaryTapUp, |
234 | this.onSecondaryTapCancel, |
235 | this.onTertiaryTapDown, |
236 | this.onTertiaryTapUp, |
237 | this.onTertiaryTapCancel, |
238 | this.onDoubleTapDown, |
239 | this.onDoubleTap, |
240 | this.onDoubleTapCancel, |
241 | this.onLongPressDown, |
242 | this.onLongPressCancel, |
243 | this.onLongPress, |
244 | this.onLongPressStart, |
245 | this.onLongPressMoveUpdate, |
246 | this.onLongPressUp, |
247 | this.onLongPressEnd, |
248 | this.onSecondaryLongPressDown, |
249 | this.onSecondaryLongPressCancel, |
250 | this.onSecondaryLongPress, |
251 | this.onSecondaryLongPressStart, |
252 | this.onSecondaryLongPressMoveUpdate, |
253 | this.onSecondaryLongPressUp, |
254 | this.onSecondaryLongPressEnd, |
255 | this.onTertiaryLongPressDown, |
256 | this.onTertiaryLongPressCancel, |
257 | this.onTertiaryLongPress, |
258 | this.onTertiaryLongPressStart, |
259 | this.onTertiaryLongPressMoveUpdate, |
260 | this.onTertiaryLongPressUp, |
261 | this.onTertiaryLongPressEnd, |
262 | this.onVerticalDragDown, |
263 | this.onVerticalDragStart, |
264 | this.onVerticalDragUpdate, |
265 | this.onVerticalDragEnd, |
266 | this.onVerticalDragCancel, |
267 | this.onHorizontalDragDown, |
268 | this.onHorizontalDragStart, |
269 | this.onHorizontalDragUpdate, |
270 | this.onHorizontalDragEnd, |
271 | this.onHorizontalDragCancel, |
272 | this.onForcePressStart, |
273 | this.onForcePressPeak, |
274 | this.onForcePressUpdate, |
275 | this.onForcePressEnd, |
276 | this.onPanDown, |
277 | this.onPanStart, |
278 | this.onPanUpdate, |
279 | this.onPanEnd, |
280 | this.onPanCancel, |
281 | this.onScaleStart, |
282 | this.onScaleUpdate, |
283 | this.onScaleEnd, |
284 | this.behavior, |
285 | this.excludeFromSemantics = false, |
286 | this.dragStartBehavior = DragStartBehavior.start, |
287 | this.trackpadScrollCausesScale = false, |
288 | this.trackpadScrollToScaleFactor = kDefaultTrackpadScrollToScaleFactor, |
289 | this.supportedDevices, |
290 | }) : assert(() { |
291 | final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null; |
292 | final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null; |
293 | final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null; |
294 | final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null; |
295 | if (havePan || haveScale) { |
296 | if (havePan && haveScale) { |
297 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
298 | ErrorSummary('Incorrect GestureDetector arguments.' ), |
299 | ErrorDescription( |
300 | 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.' , |
301 | ), |
302 | ErrorHint('Just use the scale gesture recognizer.' ), |
303 | ]); |
304 | } |
305 | final String recognizer = havePan ? 'pan' : 'scale' ; |
306 | if (haveVerticalDrag && haveHorizontalDrag) { |
307 | throw FlutterError( |
308 | 'Incorrect GestureDetector arguments.\n' |
309 | 'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer ' |
310 | 'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.' , |
311 | ); |
312 | } |
313 | } |
314 | return true; |
315 | }()); |
316 | |
317 | /// The widget below this widget in the tree. |
318 | /// |
319 | /// {@macro flutter.widgets.ProxyWidget.child} |
320 | final Widget? child; |
321 | |
322 | /// A pointer that might cause a tap with a primary button has contacted the |
323 | /// screen at a particular location. |
324 | /// |
325 | /// This is called after a short timeout, even if the winning gesture has not |
326 | /// yet been selected. If the tap gesture wins, [onTapUp] will be called, |
327 | /// otherwise [onTapCancel] will be called. |
328 | /// |
329 | /// See also: |
330 | /// |
331 | /// * [kPrimaryButton], the button this callback responds to. |
332 | final GestureTapDownCallback? onTapDown; |
333 | |
334 | /// A pointer that will trigger a tap with a primary button has stopped |
335 | /// contacting the screen at a particular location. |
336 | /// |
337 | /// This triggers immediately before [onTap] in the case of the tap gesture |
338 | /// winning. If the tap gesture did not win, [onTapCancel] is called instead. |
339 | /// |
340 | /// See also: |
341 | /// |
342 | /// * [kPrimaryButton], the button this callback responds to. |
343 | final GestureTapUpCallback? onTapUp; |
344 | |
345 | /// A tap with a primary button has occurred. |
346 | /// |
347 | /// This triggers when the tap gesture wins. If the tap gesture did not win, |
348 | /// [onTapCancel] is called instead. |
349 | /// |
350 | /// See also: |
351 | /// |
352 | /// * [kPrimaryButton], the button this callback responds to. |
353 | /// * [onTapUp], which is called at the same time but includes details |
354 | /// regarding the pointer position. |
355 | final GestureTapCallback? onTap; |
356 | |
357 | /// The pointer that previously triggered [onTapDown] will not end up causing |
358 | /// a tap. |
359 | /// |
360 | /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if |
361 | /// the tap gesture did not win. |
362 | /// |
363 | /// See also: |
364 | /// |
365 | /// * [kPrimaryButton], the button this callback responds to. |
366 | final GestureTapCancelCallback? onTapCancel; |
367 | |
368 | /// A tap with a secondary button has occurred. |
369 | /// |
370 | /// This triggers when the tap gesture wins. If the tap gesture did not win, |
371 | /// [onSecondaryTapCancel] is called instead. |
372 | /// |
373 | /// See also: |
374 | /// |
375 | /// * [kSecondaryButton], the button this callback responds to. |
376 | /// * [onSecondaryTapUp], which is called at the same time but includes details |
377 | /// regarding the pointer position. |
378 | final GestureTapCallback? onSecondaryTap; |
379 | |
380 | /// A pointer that might cause a tap with a secondary button has contacted the |
381 | /// screen at a particular location. |
382 | /// |
383 | /// This is called after a short timeout, even if the winning gesture has not |
384 | /// yet been selected. If the tap gesture wins, [onSecondaryTapUp] will be |
385 | /// called, otherwise [onSecondaryTapCancel] will be called. |
386 | /// |
387 | /// See also: |
388 | /// |
389 | /// * [kSecondaryButton], the button this callback responds to. |
390 | final GestureTapDownCallback? onSecondaryTapDown; |
391 | |
392 | /// A pointer that will trigger a tap with a secondary button has stopped |
393 | /// contacting the screen at a particular location. |
394 | /// |
395 | /// This triggers in the case of the tap gesture winning. If the tap gesture |
396 | /// did not win, [onSecondaryTapCancel] is called instead. |
397 | /// |
398 | /// See also: |
399 | /// |
400 | /// * [onSecondaryTap], a handler triggered right after this one that doesn't |
401 | /// pass any details about the tap. |
402 | /// * [kSecondaryButton], the button this callback responds to. |
403 | final GestureTapUpCallback? onSecondaryTapUp; |
404 | |
405 | /// The pointer that previously triggered [onSecondaryTapDown] will not end up |
406 | /// causing a tap. |
407 | /// |
408 | /// This is called after [onSecondaryTapDown], and instead of |
409 | /// [onSecondaryTapUp], if the tap gesture did not win. |
410 | /// |
411 | /// See also: |
412 | /// |
413 | /// * [kSecondaryButton], the button this callback responds to. |
414 | final GestureTapCancelCallback? onSecondaryTapCancel; |
415 | |
416 | /// A pointer that might cause a tap with a tertiary button has contacted the |
417 | /// screen at a particular location. |
418 | /// |
419 | /// This is called after a short timeout, even if the winning gesture has not |
420 | /// yet been selected. If the tap gesture wins, [onTertiaryTapUp] will be |
421 | /// called, otherwise [onTertiaryTapCancel] will be called. |
422 | /// |
423 | /// See also: |
424 | /// |
425 | /// * [kTertiaryButton], the button this callback responds to. |
426 | final GestureTapDownCallback? onTertiaryTapDown; |
427 | |
428 | /// A pointer that will trigger a tap with a tertiary button has stopped |
429 | /// contacting the screen at a particular location. |
430 | /// |
431 | /// This triggers in the case of the tap gesture winning. If the tap gesture |
432 | /// did not win, [onTertiaryTapCancel] is called instead. |
433 | /// |
434 | /// See also: |
435 | /// |
436 | /// * [kTertiaryButton], the button this callback responds to. |
437 | final GestureTapUpCallback? onTertiaryTapUp; |
438 | |
439 | /// The pointer that previously triggered [onTertiaryTapDown] will not end up |
440 | /// causing a tap. |
441 | /// |
442 | /// This is called after [onTertiaryTapDown], and instead of |
443 | /// [onTertiaryTapUp], if the tap gesture did not win. |
444 | /// |
445 | /// See also: |
446 | /// |
447 | /// * [kTertiaryButton], the button this callback responds to. |
448 | final GestureTapCancelCallback? onTertiaryTapCancel; |
449 | |
450 | /// A pointer that might cause a double tap has contacted the screen at a |
451 | /// particular location. |
452 | /// |
453 | /// Triggered immediately after the down event of the second tap. |
454 | /// |
455 | /// If the user completes the double tap and the gesture wins, [onDoubleTap] |
456 | /// will be called after this callback. Otherwise, [onDoubleTapCancel] will |
457 | /// be called after this callback. |
458 | /// |
459 | /// See also: |
460 | /// |
461 | /// * [kPrimaryButton], the button this callback responds to. |
462 | final GestureTapDownCallback? onDoubleTapDown; |
463 | |
464 | /// The user has tapped the screen with a primary button at the same location |
465 | /// twice in quick succession. |
466 | /// |
467 | /// See also: |
468 | /// |
469 | /// * [kPrimaryButton], the button this callback responds to. |
470 | final GestureTapCallback? onDoubleTap; |
471 | |
472 | /// The pointer that previously triggered [onDoubleTapDown] will not end up |
473 | /// causing a double tap. |
474 | /// |
475 | /// See also: |
476 | /// |
477 | /// * [kPrimaryButton], the button this callback responds to. |
478 | final GestureTapCancelCallback? onDoubleTapCancel; |
479 | |
480 | /// The pointer has contacted the screen with a primary button, which might |
481 | /// be the start of a long-press. |
482 | /// |
483 | /// This triggers after the pointer down event. |
484 | /// |
485 | /// If the user completes the long-press, and this gesture wins, |
486 | /// [onLongPressStart] will be called after this callback. Otherwise, |
487 | /// [onLongPressCancel] will be called after this callback. |
488 | /// |
489 | /// See also: |
490 | /// |
491 | /// * [kPrimaryButton], the button this callback responds to. |
492 | /// * [onSecondaryLongPressDown], a similar callback but for a secondary button. |
493 | /// * [onTertiaryLongPressDown], a similar callback but for a tertiary button. |
494 | /// * [LongPressGestureRecognizer.onLongPressDown], which exposes this |
495 | /// callback at the gesture layer. |
496 | final GestureLongPressDownCallback? onLongPressDown; |
497 | |
498 | /// A pointer that previously triggered [onLongPressDown] will not end up |
499 | /// causing a long-press. |
500 | /// |
501 | /// This triggers once the gesture loses if [onLongPressDown] has previously |
502 | /// been triggered. |
503 | /// |
504 | /// If the user completed the long-press, and the gesture won, then |
505 | /// [onLongPressStart] and [onLongPress] are called instead. |
506 | /// |
507 | /// See also: |
508 | /// |
509 | /// * [kPrimaryButton], the button this callback responds to. |
510 | /// * [LongPressGestureRecognizer.onLongPressCancel], which exposes this |
511 | /// callback at the gesture layer. |
512 | final GestureLongPressCancelCallback? onLongPressCancel; |
513 | |
514 | /// Called when a long press gesture with a primary button has been recognized. |
515 | /// |
516 | /// Triggered when a pointer has remained in contact with the screen at the |
517 | /// same location for a long period of time. |
518 | /// |
519 | /// This is equivalent to (and is called immediately after) [onLongPressStart]. |
520 | /// The only difference between the two is that this callback does not |
521 | /// contain details of the position at which the pointer initially contacted |
522 | /// the screen. |
523 | /// |
524 | /// See also: |
525 | /// |
526 | /// * [kPrimaryButton], the button this callback responds to. |
527 | /// * [LongPressGestureRecognizer.onLongPress], which exposes this |
528 | /// callback at the gesture layer. |
529 | final GestureLongPressCallback? onLongPress; |
530 | |
531 | /// Called when a long press gesture with a primary button has been recognized. |
532 | /// |
533 | /// Triggered when a pointer has remained in contact with the screen at the |
534 | /// same location for a long period of time. |
535 | /// |
536 | /// This is equivalent to (and is called immediately before) [onLongPress]. |
537 | /// The only difference between the two is that this callback contains |
538 | /// details of the position at which the pointer initially contacted the |
539 | /// screen, whereas [onLongPress] does not. |
540 | /// |
541 | /// See also: |
542 | /// |
543 | /// * [kPrimaryButton], the button this callback responds to. |
544 | /// * [LongPressGestureRecognizer.onLongPressStart], which exposes this |
545 | /// callback at the gesture layer. |
546 | final GestureLongPressStartCallback? onLongPressStart; |
547 | |
548 | /// A pointer has been drag-moved after a long-press with a primary button. |
549 | /// |
550 | /// See also: |
551 | /// |
552 | /// * [kPrimaryButton], the button this callback responds to. |
553 | /// * [LongPressGestureRecognizer.onLongPressMoveUpdate], which exposes this |
554 | /// callback at the gesture layer. |
555 | final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate; |
556 | |
557 | /// A pointer that has triggered a long-press with a primary button has |
558 | /// stopped contacting the screen. |
559 | /// |
560 | /// This is equivalent to (and is called immediately after) [onLongPressEnd]. |
561 | /// The only difference between the two is that this callback does not |
562 | /// contain details of the state of the pointer when it stopped contacting |
563 | /// the screen. |
564 | /// |
565 | /// See also: |
566 | /// |
567 | /// * [kPrimaryButton], the button this callback responds to. |
568 | /// * [LongPressGestureRecognizer.onLongPressUp], which exposes this |
569 | /// callback at the gesture layer. |
570 | final GestureLongPressUpCallback? onLongPressUp; |
571 | |
572 | /// A pointer that has triggered a long-press with a primary button has |
573 | /// stopped contacting the screen. |
574 | /// |
575 | /// This is equivalent to (and is called immediately before) [onLongPressUp]. |
576 | /// The only difference between the two is that this callback contains |
577 | /// details of the state of the pointer when it stopped contacting the |
578 | /// screen, whereas [onLongPressUp] does not. |
579 | /// |
580 | /// See also: |
581 | /// |
582 | /// * [kPrimaryButton], the button this callback responds to. |
583 | /// * [LongPressGestureRecognizer.onLongPressEnd], which exposes this |
584 | /// callback at the gesture layer. |
585 | final GestureLongPressEndCallback? onLongPressEnd; |
586 | |
587 | /// The pointer has contacted the screen with a secondary button, which might |
588 | /// be the start of a long-press. |
589 | /// |
590 | /// This triggers after the pointer down event. |
591 | /// |
592 | /// If the user completes the long-press, and this gesture wins, |
593 | /// [onSecondaryLongPressStart] will be called after this callback. Otherwise, |
594 | /// [onSecondaryLongPressCancel] will be called after this callback. |
595 | /// |
596 | /// See also: |
597 | /// |
598 | /// * [kSecondaryButton], the button this callback responds to. |
599 | /// * [onLongPressDown], a similar callback but for a secondary button. |
600 | /// * [onTertiaryLongPressDown], a similar callback but for a tertiary button. |
601 | /// * [LongPressGestureRecognizer.onSecondaryLongPressDown], which exposes |
602 | /// this callback at the gesture layer. |
603 | final GestureLongPressDownCallback? onSecondaryLongPressDown; |
604 | |
605 | /// A pointer that previously triggered [onSecondaryLongPressDown] will not |
606 | /// end up causing a long-press. |
607 | /// |
608 | /// This triggers once the gesture loses if [onSecondaryLongPressDown] has |
609 | /// previously been triggered. |
610 | /// |
611 | /// If the user completed the long-press, and the gesture won, then |
612 | /// [onSecondaryLongPressStart] and [onSecondaryLongPress] are called instead. |
613 | /// |
614 | /// See also: |
615 | /// |
616 | /// * [kSecondaryButton], the button this callback responds to. |
617 | /// * [LongPressGestureRecognizer.onSecondaryLongPressCancel], which exposes |
618 | /// this callback at the gesture layer. |
619 | final GestureLongPressCancelCallback? onSecondaryLongPressCancel; |
620 | |
621 | /// Called when a long press gesture with a secondary button has been |
622 | /// recognized. |
623 | /// |
624 | /// Triggered when a pointer has remained in contact with the screen at the |
625 | /// same location for a long period of time. |
626 | /// |
627 | /// This is equivalent to (and is called immediately after) |
628 | /// [onSecondaryLongPressStart]. The only difference between the two is that |
629 | /// this callback does not contain details of the position at which the |
630 | /// pointer initially contacted the screen. |
631 | /// |
632 | /// See also: |
633 | /// |
634 | /// * [kSecondaryButton], the button this callback responds to. |
635 | /// * [LongPressGestureRecognizer.onSecondaryLongPress], which exposes |
636 | /// this callback at the gesture layer. |
637 | final GestureLongPressCallback? onSecondaryLongPress; |
638 | |
639 | /// Called when a long press gesture with a secondary button has been |
640 | /// recognized. |
641 | /// |
642 | /// Triggered when a pointer has remained in contact with the screen at the |
643 | /// same location for a long period of time. |
644 | /// |
645 | /// This is equivalent to (and is called immediately before) |
646 | /// [onSecondaryLongPress]. The only difference between the two is that this |
647 | /// callback contains details of the position at which the pointer initially |
648 | /// contacted the screen, whereas [onSecondaryLongPress] does not. |
649 | /// |
650 | /// See also: |
651 | /// |
652 | /// * [kSecondaryButton], the button this callback responds to. |
653 | /// * [LongPressGestureRecognizer.onSecondaryLongPressStart], which exposes |
654 | /// this callback at the gesture layer. |
655 | final GestureLongPressStartCallback? onSecondaryLongPressStart; |
656 | |
657 | /// A pointer has been drag-moved after a long press with a secondary button. |
658 | /// |
659 | /// See also: |
660 | /// |
661 | /// * [kSecondaryButton], the button this callback responds to. |
662 | /// * [LongPressGestureRecognizer.onSecondaryLongPressMoveUpdate], which exposes |
663 | /// this callback at the gesture layer. |
664 | final GestureLongPressMoveUpdateCallback? onSecondaryLongPressMoveUpdate; |
665 | |
666 | /// A pointer that has triggered a long-press with a secondary button has |
667 | /// stopped contacting the screen. |
668 | /// |
669 | /// This is equivalent to (and is called immediately after) |
670 | /// [onSecondaryLongPressEnd]. The only difference between the two is that |
671 | /// this callback does not contain details of the state of the pointer when |
672 | /// it stopped contacting the screen. |
673 | /// |
674 | /// See also: |
675 | /// |
676 | /// * [kSecondaryButton], the button this callback responds to. |
677 | /// * [LongPressGestureRecognizer.onSecondaryLongPressUp], which exposes |
678 | /// this callback at the gesture layer. |
679 | final GestureLongPressUpCallback? onSecondaryLongPressUp; |
680 | |
681 | /// A pointer that has triggered a long-press with a secondary button has |
682 | /// stopped contacting the screen. |
683 | /// |
684 | /// This is equivalent to (and is called immediately before) |
685 | /// [onSecondaryLongPressUp]. The only difference between the two is that |
686 | /// this callback contains details of the state of the pointer when it |
687 | /// stopped contacting the screen, whereas [onSecondaryLongPressUp] does not. |
688 | /// |
689 | /// See also: |
690 | /// |
691 | /// * [kSecondaryButton], the button this callback responds to. |
692 | /// * [LongPressGestureRecognizer.onSecondaryLongPressEnd], which exposes |
693 | /// this callback at the gesture layer. |
694 | final GestureLongPressEndCallback? onSecondaryLongPressEnd; |
695 | |
696 | /// The pointer has contacted the screen with a tertiary button, which might |
697 | /// be the start of a long-press. |
698 | /// |
699 | /// This triggers after the pointer down event. |
700 | /// |
701 | /// If the user completes the long-press, and this gesture wins, |
702 | /// [onTertiaryLongPressStart] will be called after this callback. Otherwise, |
703 | /// [onTertiaryLongPressCancel] will be called after this callback. |
704 | /// |
705 | /// See also: |
706 | /// |
707 | /// * [kTertiaryButton], the button this callback responds to. |
708 | /// * [onLongPressDown], a similar callback but for a primary button. |
709 | /// * [onSecondaryLongPressDown], a similar callback but for a secondary button. |
710 | /// * [LongPressGestureRecognizer.onTertiaryLongPressDown], which exposes |
711 | /// this callback at the gesture layer. |
712 | final GestureLongPressDownCallback? onTertiaryLongPressDown; |
713 | |
714 | /// A pointer that previously triggered [onTertiaryLongPressDown] will not |
715 | /// end up causing a long-press. |
716 | /// |
717 | /// This triggers once the gesture loses if [onTertiaryLongPressDown] has |
718 | /// previously been triggered. |
719 | /// |
720 | /// If the user completed the long-press, and the gesture won, then |
721 | /// [onTertiaryLongPressStart] and [onTertiaryLongPress] are called instead. |
722 | /// |
723 | /// See also: |
724 | /// |
725 | /// * [kTertiaryButton], the button this callback responds to. |
726 | /// * [LongPressGestureRecognizer.onTertiaryLongPressCancel], which exposes |
727 | /// this callback at the gesture layer. |
728 | final GestureLongPressCancelCallback? onTertiaryLongPressCancel; |
729 | |
730 | /// Called when a long press gesture with a tertiary button has been |
731 | /// recognized. |
732 | /// |
733 | /// Triggered when a pointer has remained in contact with the screen at the |
734 | /// same location for a long period of time. |
735 | /// |
736 | /// This is equivalent to (and is called immediately after) |
737 | /// [onTertiaryLongPressStart]. The only difference between the two is that |
738 | /// this callback does not contain details of the position at which the |
739 | /// pointer initially contacted the screen. |
740 | /// |
741 | /// See also: |
742 | /// |
743 | /// * [kTertiaryButton], the button this callback responds to. |
744 | /// * [LongPressGestureRecognizer.onTertiaryLongPress], which exposes |
745 | /// this callback at the gesture layer. |
746 | final GestureLongPressCallback? onTertiaryLongPress; |
747 | |
748 | /// Called when a long press gesture with a tertiary button has been |
749 | /// recognized. |
750 | /// |
751 | /// Triggered when a pointer has remained in contact with the screen at the |
752 | /// same location for a long period of time. |
753 | /// |
754 | /// This is equivalent to (and is called immediately before) |
755 | /// [onTertiaryLongPress]. The only difference between the two is that this |
756 | /// callback contains details of the position at which the pointer initially |
757 | /// contacted the screen, whereas [onTertiaryLongPress] does not. |
758 | /// |
759 | /// See also: |
760 | /// |
761 | /// * [kTertiaryButton], the button this callback responds to. |
762 | /// * [LongPressGestureRecognizer.onTertiaryLongPressStart], which exposes |
763 | /// this callback at the gesture layer. |
764 | final GestureLongPressStartCallback? onTertiaryLongPressStart; |
765 | |
766 | /// A pointer has been drag-moved after a long press with a tertiary button. |
767 | /// |
768 | /// See also: |
769 | /// |
770 | /// * [kTertiaryButton], the button this callback responds to. |
771 | /// * [LongPressGestureRecognizer.onTertiaryLongPressMoveUpdate], which exposes |
772 | /// this callback at the gesture layer. |
773 | final GestureLongPressMoveUpdateCallback? onTertiaryLongPressMoveUpdate; |
774 | |
775 | /// A pointer that has triggered a long-press with a tertiary button has |
776 | /// stopped contacting the screen. |
777 | /// |
778 | /// This is equivalent to (and is called immediately after) |
779 | /// [onTertiaryLongPressEnd]. The only difference between the two is that |
780 | /// this callback does not contain details of the state of the pointer when |
781 | /// it stopped contacting the screen. |
782 | /// |
783 | /// See also: |
784 | /// |
785 | /// * [kTertiaryButton], the button this callback responds to. |
786 | /// * [LongPressGestureRecognizer.onTertiaryLongPressUp], which exposes |
787 | /// this callback at the gesture layer. |
788 | final GestureLongPressUpCallback? onTertiaryLongPressUp; |
789 | |
790 | /// A pointer that has triggered a long-press with a tertiary button has |
791 | /// stopped contacting the screen. |
792 | /// |
793 | /// This is equivalent to (and is called immediately before) |
794 | /// [onTertiaryLongPressUp]. The only difference between the two is that |
795 | /// this callback contains details of the state of the pointer when it |
796 | /// stopped contacting the screen, whereas [onTertiaryLongPressUp] does not. |
797 | /// |
798 | /// See also: |
799 | /// |
800 | /// * [kTertiaryButton], the button this callback responds to. |
801 | /// * [LongPressGestureRecognizer.onTertiaryLongPressEnd], which exposes |
802 | /// this callback at the gesture layer. |
803 | final GestureLongPressEndCallback? onTertiaryLongPressEnd; |
804 | |
805 | /// A pointer has contacted the screen with a primary button and might begin |
806 | /// to move vertically. |
807 | /// |
808 | /// See also: |
809 | /// |
810 | /// * [kPrimaryButton], the button this callback responds to. |
811 | final GestureDragDownCallback? onVerticalDragDown; |
812 | |
813 | /// A pointer has contacted the screen with a primary button and has begun to |
814 | /// move vertically. |
815 | /// |
816 | /// See also: |
817 | /// |
818 | /// * [kPrimaryButton], the button this callback responds to. |
819 | final GestureDragStartCallback? onVerticalDragStart; |
820 | |
821 | /// A pointer that is in contact with the screen with a primary button and |
822 | /// moving vertically has moved in the vertical direction. |
823 | /// |
824 | /// See also: |
825 | /// |
826 | /// * [kPrimaryButton], the button this callback responds to. |
827 | final GestureDragUpdateCallback? onVerticalDragUpdate; |
828 | |
829 | /// A pointer that was previously in contact with the screen with a primary |
830 | /// button and moving vertically is no longer in contact with the screen and |
831 | /// was moving at a specific velocity when it stopped contacting the screen. |
832 | /// |
833 | /// See also: |
834 | /// |
835 | /// * [kPrimaryButton], the button this callback responds to. |
836 | final GestureDragEndCallback? onVerticalDragEnd; |
837 | |
838 | /// The pointer that previously triggered [onVerticalDragDown] did not |
839 | /// complete. |
840 | /// |
841 | /// See also: |
842 | /// |
843 | /// * [kPrimaryButton], the button this callback responds to. |
844 | final GestureDragCancelCallback? onVerticalDragCancel; |
845 | |
846 | /// A pointer has contacted the screen with a primary button and might begin |
847 | /// to move horizontally. |
848 | /// |
849 | /// See also: |
850 | /// |
851 | /// * [kPrimaryButton], the button this callback responds to. |
852 | final GestureDragDownCallback? onHorizontalDragDown; |
853 | |
854 | /// A pointer has contacted the screen with a primary button and has begun to |
855 | /// move horizontally. |
856 | /// |
857 | /// See also: |
858 | /// |
859 | /// * [kPrimaryButton], the button this callback responds to. |
860 | final GestureDragStartCallback? onHorizontalDragStart; |
861 | |
862 | /// A pointer that is in contact with the screen with a primary button and |
863 | /// moving horizontally has moved in the horizontal direction. |
864 | /// |
865 | /// See also: |
866 | /// |
867 | /// * [kPrimaryButton], the button this callback responds to. |
868 | final GestureDragUpdateCallback? onHorizontalDragUpdate; |
869 | |
870 | /// A pointer that was previously in contact with the screen with a primary |
871 | /// button and moving horizontally is no longer in contact with the screen and |
872 | /// was moving at a specific velocity when it stopped contacting the screen. |
873 | /// |
874 | /// See also: |
875 | /// |
876 | /// * [kPrimaryButton], the button this callback responds to. |
877 | final GestureDragEndCallback? onHorizontalDragEnd; |
878 | |
879 | /// The pointer that previously triggered [onHorizontalDragDown] did not |
880 | /// complete. |
881 | /// |
882 | /// See also: |
883 | /// |
884 | /// * [kPrimaryButton], the button this callback responds to. |
885 | final GestureDragCancelCallback? onHorizontalDragCancel; |
886 | |
887 | /// A pointer has contacted the screen with a primary button and might begin |
888 | /// to move. |
889 | /// |
890 | /// See also: |
891 | /// |
892 | /// * [kPrimaryButton], the button this callback responds to. |
893 | final GestureDragDownCallback? onPanDown; |
894 | |
895 | /// A pointer has contacted the screen with a primary button and has begun to |
896 | /// move. |
897 | /// |
898 | /// See also: |
899 | /// |
900 | /// * [kPrimaryButton], the button this callback responds to. |
901 | final GestureDragStartCallback? onPanStart; |
902 | |
903 | /// A pointer that is in contact with the screen with a primary button and |
904 | /// moving has moved again. |
905 | /// |
906 | /// See also: |
907 | /// |
908 | /// * [kPrimaryButton], the button this callback responds to. |
909 | final GestureDragUpdateCallback? onPanUpdate; |
910 | |
911 | /// A pointer that was previously in contact with the screen with a primary |
912 | /// button and moving is no longer in contact with the screen and was moving |
913 | /// at a specific velocity when it stopped contacting the screen. |
914 | /// |
915 | /// See also: |
916 | /// |
917 | /// * [kPrimaryButton], the button this callback responds to. |
918 | final GestureDragEndCallback? onPanEnd; |
919 | |
920 | /// The pointer that previously triggered [onPanDown] did not complete. |
921 | /// |
922 | /// See also: |
923 | /// |
924 | /// * [kPrimaryButton], the button this callback responds to. |
925 | final GestureDragCancelCallback? onPanCancel; |
926 | |
927 | /// The pointers in contact with the screen have established a focal point and |
928 | /// initial scale of 1.0. |
929 | final GestureScaleStartCallback? onScaleStart; |
930 | |
931 | /// The pointers in contact with the screen have indicated a new focal point |
932 | /// and/or scale. |
933 | final GestureScaleUpdateCallback? onScaleUpdate; |
934 | |
935 | /// The pointers are no longer in contact with the screen. |
936 | final GestureScaleEndCallback? onScaleEnd; |
937 | |
938 | /// The pointer is in contact with the screen and has pressed with sufficient |
939 | /// force to initiate a force press. The amount of force is at least |
940 | /// [ForcePressGestureRecognizer.startPressure]. |
941 | /// |
942 | /// This callback will only be fired on devices with pressure |
943 | /// detecting screens. |
944 | final GestureForcePressStartCallback? onForcePressStart; |
945 | |
946 | /// The pointer is in contact with the screen and has pressed with the maximum |
947 | /// force. The amount of force is at least |
948 | /// [ForcePressGestureRecognizer.peakPressure]. |
949 | /// |
950 | /// This callback will only be fired on devices with pressure |
951 | /// detecting screens. |
952 | final GestureForcePressPeakCallback? onForcePressPeak; |
953 | |
954 | /// A pointer is in contact with the screen, has previously passed the |
955 | /// [ForcePressGestureRecognizer.startPressure] and is either moving on the |
956 | /// plane of the screen, pressing the screen with varying forces or both |
957 | /// simultaneously. |
958 | /// |
959 | /// This callback will only be fired on devices with pressure |
960 | /// detecting screens. |
961 | final GestureForcePressUpdateCallback? onForcePressUpdate; |
962 | |
963 | /// The pointer tracked by [onForcePressStart] is no longer in contact with the screen. |
964 | /// |
965 | /// This callback will only be fired on devices with pressure |
966 | /// detecting screens. |
967 | final GestureForcePressEndCallback? onForcePressEnd; |
968 | |
969 | /// How this gesture detector should behave during hit testing when deciding |
970 | /// how the hit test propagates to children and whether to consider targets |
971 | /// behind this one. |
972 | /// |
973 | /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and |
974 | /// [HitTestBehavior.translucent] if child is null. |
975 | /// |
976 | /// See [HitTestBehavior] for the allowed values and their meanings. |
977 | final HitTestBehavior? behavior; |
978 | |
979 | /// Whether to exclude these gestures from the semantics tree. For |
980 | /// example, the long-press gesture for showing a tooltip is |
981 | /// excluded because the tooltip itself is included in the semantics |
982 | /// tree directly and so having a gesture to show it would result in |
983 | /// duplication of information. |
984 | final bool excludeFromSemantics; |
985 | |
986 | /// Determines the way that drag start behavior is handled. |
987 | /// |
988 | /// If set to [DragStartBehavior.start], gesture drag behavior will |
989 | /// begin at the position where the drag gesture won the arena. If set to |
990 | /// [DragStartBehavior.down] it will begin at the position where a down event |
991 | /// is first detected. |
992 | /// |
993 | /// In general, setting this to [DragStartBehavior.start] will make drag |
994 | /// animation smoother and setting it to [DragStartBehavior.down] will make |
995 | /// drag behavior feel slightly more reactive. |
996 | /// |
997 | /// By default, the drag start behavior is [DragStartBehavior.start]. |
998 | /// |
999 | /// Only the [DragGestureRecognizer.onStart] callbacks for the |
1000 | /// [VerticalDragGestureRecognizer], [HorizontalDragGestureRecognizer] and |
1001 | /// [PanGestureRecognizer] are affected by this setting. |
1002 | /// |
1003 | /// See also: |
1004 | /// |
1005 | /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. |
1006 | final DragStartBehavior dragStartBehavior; |
1007 | |
1008 | /// The kind of devices that are allowed to be recognized. |
1009 | /// |
1010 | /// If set to null, events from all device types will be recognized. Defaults to null. |
1011 | final Set<PointerDeviceKind>? supportedDevices; |
1012 | |
1013 | /// {@macro flutter.gestures.scale.trackpadScrollCausesScale} |
1014 | final bool trackpadScrollCausesScale; |
1015 | |
1016 | /// {@macro flutter.gestures.scale.trackpadScrollToScaleFactor} |
1017 | final Offset trackpadScrollToScaleFactor; |
1018 | |
1019 | @override |
1020 | Widget build(BuildContext context) { |
1021 | final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{}; |
1022 | final DeviceGestureSettings? gestureSettings = MediaQuery.maybeGestureSettingsOf(context); |
1023 | |
1024 | if (onTapDown != null || |
1025 | onTapUp != null || |
1026 | onTap != null || |
1027 | onTapCancel != null || |
1028 | onSecondaryTap != null || |
1029 | onSecondaryTapDown != null || |
1030 | onSecondaryTapUp != null || |
1031 | onSecondaryTapCancel != null|| |
1032 | onTertiaryTapDown != null || |
1033 | onTertiaryTapUp != null || |
1034 | onTertiaryTapCancel != null |
1035 | ) { |
1036 | gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( |
1037 | () => TapGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1038 | (TapGestureRecognizer instance) { |
1039 | instance |
1040 | ..onTapDown = onTapDown |
1041 | ..onTapUp = onTapUp |
1042 | ..onTap = onTap |
1043 | ..onTapCancel = onTapCancel |
1044 | ..onSecondaryTap = onSecondaryTap |
1045 | ..onSecondaryTapDown = onSecondaryTapDown |
1046 | ..onSecondaryTapUp = onSecondaryTapUp |
1047 | ..onSecondaryTapCancel = onSecondaryTapCancel |
1048 | ..onTertiaryTapDown = onTertiaryTapDown |
1049 | ..onTertiaryTapUp = onTertiaryTapUp |
1050 | ..onTertiaryTapCancel = onTertiaryTapCancel |
1051 | ..gestureSettings = gestureSettings |
1052 | ..supportedDevices = supportedDevices; |
1053 | }, |
1054 | ); |
1055 | } |
1056 | |
1057 | if (onDoubleTap != null || |
1058 | onDoubleTapDown != null || |
1059 | onDoubleTapCancel != null) { |
1060 | gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>( |
1061 | () => DoubleTapGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1062 | (DoubleTapGestureRecognizer instance) { |
1063 | instance |
1064 | ..onDoubleTapDown = onDoubleTapDown |
1065 | ..onDoubleTap = onDoubleTap |
1066 | ..onDoubleTapCancel = onDoubleTapCancel |
1067 | ..gestureSettings = gestureSettings |
1068 | ..supportedDevices = supportedDevices; |
1069 | }, |
1070 | ); |
1071 | } |
1072 | |
1073 | if (onLongPressDown != null || |
1074 | onLongPressCancel != null || |
1075 | onLongPress != null || |
1076 | onLongPressStart != null || |
1077 | onLongPressMoveUpdate != null || |
1078 | onLongPressUp != null || |
1079 | onLongPressEnd != null || |
1080 | onSecondaryLongPressDown != null || |
1081 | onSecondaryLongPressCancel != null || |
1082 | onSecondaryLongPress != null || |
1083 | onSecondaryLongPressStart != null || |
1084 | onSecondaryLongPressMoveUpdate != null || |
1085 | onSecondaryLongPressUp != null || |
1086 | onSecondaryLongPressEnd != null || |
1087 | onTertiaryLongPressDown != null || |
1088 | onTertiaryLongPressCancel != null || |
1089 | onTertiaryLongPress != null || |
1090 | onTertiaryLongPressStart != null || |
1091 | onTertiaryLongPressMoveUpdate != null || |
1092 | onTertiaryLongPressUp != null || |
1093 | onTertiaryLongPressEnd != null) { |
1094 | gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>( |
1095 | () => LongPressGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1096 | (LongPressGestureRecognizer instance) { |
1097 | instance |
1098 | ..onLongPressDown = onLongPressDown |
1099 | ..onLongPressCancel = onLongPressCancel |
1100 | ..onLongPress = onLongPress |
1101 | ..onLongPressStart = onLongPressStart |
1102 | ..onLongPressMoveUpdate = onLongPressMoveUpdate |
1103 | ..onLongPressUp = onLongPressUp |
1104 | ..onLongPressEnd = onLongPressEnd |
1105 | ..onSecondaryLongPressDown = onSecondaryLongPressDown |
1106 | ..onSecondaryLongPressCancel = onSecondaryLongPressCancel |
1107 | ..onSecondaryLongPress = onSecondaryLongPress |
1108 | ..onSecondaryLongPressStart = onSecondaryLongPressStart |
1109 | ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate |
1110 | ..onSecondaryLongPressUp = onSecondaryLongPressUp |
1111 | ..onSecondaryLongPressEnd = onSecondaryLongPressEnd |
1112 | ..onTertiaryLongPressDown = onTertiaryLongPressDown |
1113 | ..onTertiaryLongPressCancel = onTertiaryLongPressCancel |
1114 | ..onTertiaryLongPress = onTertiaryLongPress |
1115 | ..onTertiaryLongPressStart = onTertiaryLongPressStart |
1116 | ..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate |
1117 | ..onTertiaryLongPressUp = onTertiaryLongPressUp |
1118 | ..onTertiaryLongPressEnd = onTertiaryLongPressEnd |
1119 | ..gestureSettings = gestureSettings |
1120 | ..supportedDevices = supportedDevices; |
1121 | }, |
1122 | ); |
1123 | } |
1124 | |
1125 | if (onVerticalDragDown != null || |
1126 | onVerticalDragStart != null || |
1127 | onVerticalDragUpdate != null || |
1128 | onVerticalDragEnd != null || |
1129 | onVerticalDragCancel != null) { |
1130 | gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>( |
1131 | () => VerticalDragGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1132 | (VerticalDragGestureRecognizer instance) { |
1133 | instance |
1134 | ..onDown = onVerticalDragDown |
1135 | ..onStart = onVerticalDragStart |
1136 | ..onUpdate = onVerticalDragUpdate |
1137 | ..onEnd = onVerticalDragEnd |
1138 | ..onCancel = onVerticalDragCancel |
1139 | ..dragStartBehavior = dragStartBehavior |
1140 | ..gestureSettings = gestureSettings |
1141 | ..supportedDevices = supportedDevices; |
1142 | }, |
1143 | ); |
1144 | } |
1145 | |
1146 | if (onHorizontalDragDown != null || |
1147 | onHorizontalDragStart != null || |
1148 | onHorizontalDragUpdate != null || |
1149 | onHorizontalDragEnd != null || |
1150 | onHorizontalDragCancel != null) { |
1151 | gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>( |
1152 | () => HorizontalDragGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1153 | (HorizontalDragGestureRecognizer instance) { |
1154 | instance |
1155 | ..onDown = onHorizontalDragDown |
1156 | ..onStart = onHorizontalDragStart |
1157 | ..onUpdate = onHorizontalDragUpdate |
1158 | ..onEnd = onHorizontalDragEnd |
1159 | ..onCancel = onHorizontalDragCancel |
1160 | ..dragStartBehavior = dragStartBehavior |
1161 | ..gestureSettings = gestureSettings |
1162 | ..supportedDevices = supportedDevices; |
1163 | }, |
1164 | ); |
1165 | } |
1166 | |
1167 | if (onPanDown != null || |
1168 | onPanStart != null || |
1169 | onPanUpdate != null || |
1170 | onPanEnd != null || |
1171 | onPanCancel != null) { |
1172 | gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>( |
1173 | () => PanGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1174 | (PanGestureRecognizer instance) { |
1175 | instance |
1176 | ..onDown = onPanDown |
1177 | ..onStart = onPanStart |
1178 | ..onUpdate = onPanUpdate |
1179 | ..onEnd = onPanEnd |
1180 | ..onCancel = onPanCancel |
1181 | ..dragStartBehavior = dragStartBehavior |
1182 | ..gestureSettings = gestureSettings |
1183 | ..supportedDevices = supportedDevices; |
1184 | }, |
1185 | ); |
1186 | } |
1187 | |
1188 | if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) { |
1189 | gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>( |
1190 | () => ScaleGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1191 | (ScaleGestureRecognizer instance) { |
1192 | instance |
1193 | ..onStart = onScaleStart |
1194 | ..onUpdate = onScaleUpdate |
1195 | ..onEnd = onScaleEnd |
1196 | ..dragStartBehavior = dragStartBehavior |
1197 | ..gestureSettings = gestureSettings |
1198 | ..trackpadScrollCausesScale = trackpadScrollCausesScale |
1199 | ..trackpadScrollToScaleFactor = trackpadScrollToScaleFactor |
1200 | ..supportedDevices = supportedDevices; |
1201 | }, |
1202 | ); |
1203 | } |
1204 | |
1205 | if (onForcePressStart != null || |
1206 | onForcePressPeak != null || |
1207 | onForcePressUpdate != null || |
1208 | onForcePressEnd != null) { |
1209 | gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>( |
1210 | () => ForcePressGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1211 | (ForcePressGestureRecognizer instance) { |
1212 | instance |
1213 | ..onStart = onForcePressStart |
1214 | ..onPeak = onForcePressPeak |
1215 | ..onUpdate = onForcePressUpdate |
1216 | ..onEnd = onForcePressEnd |
1217 | ..gestureSettings = gestureSettings |
1218 | ..supportedDevices = supportedDevices; |
1219 | }, |
1220 | ); |
1221 | } |
1222 | |
1223 | return RawGestureDetector( |
1224 | gestures: gestures, |
1225 | behavior: behavior, |
1226 | excludeFromSemantics: excludeFromSemantics, |
1227 | child: child, |
1228 | ); |
1229 | } |
1230 | |
1231 | @override |
1232 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1233 | super.debugFillProperties(properties); |
1234 | properties.add(EnumProperty<DragStartBehavior>('startBehavior' , dragStartBehavior)); |
1235 | } |
1236 | } |
1237 | |
1238 | /// A widget that detects gestures described by the given gesture |
1239 | /// factories. |
1240 | /// |
1241 | /// For common gestures, use a [GestureDetector]. |
1242 | /// [RawGestureDetector] is useful primarily when developing your |
1243 | /// own gesture recognizers. |
1244 | /// |
1245 | /// Configuring the gesture recognizers requires a carefully constructed map, as |
1246 | /// described in [gestures] and as shown in the example below. |
1247 | /// |
1248 | /// {@tool snippet} |
1249 | /// |
1250 | /// This example shows how to hook up a [TapGestureRecognizer]. It assumes that |
1251 | /// the code is being used inside a [State] object with a `_last` field that is |
1252 | /// then displayed as the child of the gesture detector. |
1253 | /// |
1254 | /// ```dart |
1255 | /// RawGestureDetector( |
1256 | /// gestures: <Type, GestureRecognizerFactory>{ |
1257 | /// TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( |
1258 | /// () => TapGestureRecognizer(), |
1259 | /// (TapGestureRecognizer instance) { |
1260 | /// instance |
1261 | /// ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); } |
1262 | /// ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); } |
1263 | /// ..onTap = () { setState(() { _last = 'tap'; }); } |
1264 | /// ..onTapCancel = () { setState(() { _last = 'cancel'; }); }; |
1265 | /// }, |
1266 | /// ), |
1267 | /// }, |
1268 | /// child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)), |
1269 | /// ) |
1270 | /// ``` |
1271 | /// {@end-tool} |
1272 | /// |
1273 | /// See also: |
1274 | /// |
1275 | /// * [GestureDetector], a less flexible but much simpler widget that does the same thing. |
1276 | /// * [Listener], a widget that reports raw pointer events. |
1277 | /// * [GestureRecognizer], the class that you extend to create a custom gesture recognizer. |
1278 | class RawGestureDetector extends StatefulWidget { |
1279 | /// Creates a widget that detects gestures. |
1280 | /// |
1281 | /// Gesture detectors can contribute semantic information to the tree that is |
1282 | /// used by assistive technology. The behavior can be configured by |
1283 | /// [semantics], or disabled with [excludeFromSemantics]. |
1284 | const RawGestureDetector({ |
1285 | super.key, |
1286 | this.child, |
1287 | this.gestures = const <Type, GestureRecognizerFactory>{}, |
1288 | this.behavior, |
1289 | this.excludeFromSemantics = false, |
1290 | this.semantics, |
1291 | }); |
1292 | |
1293 | /// The widget below this widget in the tree. |
1294 | /// |
1295 | /// {@macro flutter.widgets.ProxyWidget.child} |
1296 | final Widget? child; |
1297 | |
1298 | /// The gestures that this widget will attempt to recognize. |
1299 | /// |
1300 | /// This should be a map from [GestureRecognizer] subclasses to |
1301 | /// [GestureRecognizerFactory] subclasses specialized with the same type. |
1302 | /// |
1303 | /// This value can be late-bound at layout time using |
1304 | /// [RawGestureDetectorState.replaceGestureRecognizers]. |
1305 | final Map<Type, GestureRecognizerFactory> gestures; |
1306 | |
1307 | /// How this gesture detector should behave during hit testing. |
1308 | /// |
1309 | /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and |
1310 | /// [HitTestBehavior.translucent] if child is null. |
1311 | final HitTestBehavior? behavior; |
1312 | |
1313 | /// Whether to exclude these gestures from the semantics tree. For |
1314 | /// example, the long-press gesture for showing a tooltip is |
1315 | /// excluded because the tooltip itself is included in the semantics |
1316 | /// tree directly and so having a gesture to show it would result in |
1317 | /// duplication of information. |
1318 | final bool excludeFromSemantics; |
1319 | |
1320 | /// Describes the semantics notations that should be added to the underlying |
1321 | /// render object [RenderSemanticsGestureHandler]. |
1322 | /// |
1323 | /// It has no effect if [excludeFromSemantics] is true. |
1324 | /// |
1325 | /// When [semantics] is null, [RawGestureDetector] will fall back to a |
1326 | /// default delegate which checks if the detector owns certain gesture |
1327 | /// recognizers and calls their callbacks if they exist: |
1328 | /// |
1329 | /// * During a semantic tap, it calls [TapGestureRecognizer]'s |
1330 | /// `onTapDown`, `onTapUp`, and `onTap`. |
1331 | /// * During a semantic long press, it calls [LongPressGestureRecognizer]'s |
1332 | /// `onLongPressDown`, `onLongPressStart`, `onLongPress`, `onLongPressEnd` |
1333 | /// and `onLongPressUp`. |
1334 | /// * During a semantic horizontal drag, it calls [HorizontalDragGestureRecognizer]'s |
1335 | /// `onDown`, `onStart`, `onUpdate` and `onEnd`, then |
1336 | /// [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`. |
1337 | /// * During a semantic vertical drag, it calls [VerticalDragGestureRecognizer]'s |
1338 | /// `onDown`, `onStart`, `onUpdate` and `onEnd`, then |
1339 | /// [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`. |
1340 | /// |
1341 | /// {@tool snippet} |
1342 | /// This custom gesture detector listens to force presses, while also allows |
1343 | /// the same callback to be triggered by semantic long presses. |
1344 | /// |
1345 | /// ```dart |
1346 | /// class ForcePressGestureDetectorWithSemantics extends StatelessWidget { |
1347 | /// const ForcePressGestureDetectorWithSemantics({ |
1348 | /// super.key, |
1349 | /// required this.child, |
1350 | /// required this.onForcePress, |
1351 | /// }); |
1352 | /// |
1353 | /// final Widget child; |
1354 | /// final VoidCallback onForcePress; |
1355 | /// |
1356 | /// @override |
1357 | /// Widget build(BuildContext context) { |
1358 | /// return RawGestureDetector( |
1359 | /// gestures: <Type, GestureRecognizerFactory>{ |
1360 | /// ForcePressGestureRecognizer: GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>( |
1361 | /// () => ForcePressGestureRecognizer(debugOwner: this), |
1362 | /// (ForcePressGestureRecognizer instance) { |
1363 | /// instance.onStart = (_) => onForcePress(); |
1364 | /// } |
1365 | /// ), |
1366 | /// }, |
1367 | /// behavior: HitTestBehavior.opaque, |
1368 | /// semantics: _LongPressSemanticsDelegate(onForcePress), |
1369 | /// child: child, |
1370 | /// ); |
1371 | /// } |
1372 | /// } |
1373 | /// |
1374 | /// class _LongPressSemanticsDelegate extends SemanticsGestureDelegate { |
1375 | /// _LongPressSemanticsDelegate(this.onLongPress); |
1376 | /// |
1377 | /// VoidCallback onLongPress; |
1378 | /// |
1379 | /// @override |
1380 | /// void assignSemantics(RenderSemanticsGestureHandler renderObject) { |
1381 | /// renderObject.onLongPress = onLongPress; |
1382 | /// } |
1383 | /// } |
1384 | /// ``` |
1385 | /// {@end-tool} |
1386 | final SemanticsGestureDelegate? semantics; |
1387 | |
1388 | @override |
1389 | RawGestureDetectorState createState() => RawGestureDetectorState(); |
1390 | } |
1391 | |
1392 | /// State for a [RawGestureDetector]. |
1393 | class RawGestureDetectorState extends State<RawGestureDetector> { |
1394 | Map<Type, GestureRecognizer>? _recognizers = const <Type, GestureRecognizer>{}; |
1395 | SemanticsGestureDelegate? _semantics; |
1396 | |
1397 | @override |
1398 | void initState() { |
1399 | super.initState(); |
1400 | _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this); |
1401 | _syncAll(widget.gestures); |
1402 | } |
1403 | |
1404 | @override |
1405 | void didUpdateWidget(RawGestureDetector oldWidget) { |
1406 | super.didUpdateWidget(oldWidget); |
1407 | if (!(oldWidget.semantics == null && widget.semantics == null)) { |
1408 | _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this); |
1409 | } |
1410 | _syncAll(widget.gestures); |
1411 | } |
1412 | |
1413 | /// This method can be called after the build phase, during the |
1414 | /// layout of the nearest descendant [RenderObjectWidget] of the |
1415 | /// gesture detector, to update the list of active gesture |
1416 | /// recognizers. |
1417 | /// |
1418 | /// The typical use case is [Scrollable]s, which put their viewport |
1419 | /// in their gesture detector, and then need to know the dimensions |
1420 | /// of the viewport and the viewport's child to determine whether |
1421 | /// the gesture detector should be enabled. |
1422 | /// |
1423 | /// The argument should follow the same conventions as |
1424 | /// [RawGestureDetector.gestures]. It acts like a temporary replacement for |
1425 | /// that value until the next build. |
1426 | void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) { |
1427 | assert(() { |
1428 | if (!context.findRenderObject()!.owner!.debugDoingLayout) { |
1429 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
1430 | ErrorSummary('Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.' ), |
1431 | ErrorDescription('The replaceGestureRecognizers() method can only be called during the layout phase.' ), |
1432 | ErrorHint( |
1433 | 'To set the gesture recognizers at other times, trigger a new build using setState() ' |
1434 | 'and provide the new gesture recognizers as constructor arguments to the corresponding ' |
1435 | 'RawGestureDetector or GestureDetector object.' , |
1436 | ), |
1437 | ]); |
1438 | } |
1439 | return true; |
1440 | }()); |
1441 | _syncAll(gestures); |
1442 | if (!widget.excludeFromSemantics) { |
1443 | final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject()! as RenderSemanticsGestureHandler; |
1444 | _updateSemanticsForRenderObject(semanticsGestureHandler); |
1445 | } |
1446 | } |
1447 | |
1448 | /// This method can be called to filter the list of available semantic actions, |
1449 | /// after the render object was created. |
1450 | /// |
1451 | /// The actual filtering is happening in the next frame and a frame will be |
1452 | /// scheduled if non is pending. |
1453 | /// |
1454 | /// This is used by [Scrollable] to configure system accessibility tools so |
1455 | /// that they know in which direction a particular list can be scrolled. |
1456 | /// |
1457 | /// If this is never called, then the actions are not filtered. If the list of |
1458 | /// actions to filter changes, it must be called again. |
1459 | void replaceSemanticsActions(Set<SemanticsAction> actions) { |
1460 | if (widget.excludeFromSemantics) { |
1461 | return; |
1462 | } |
1463 | |
1464 | final RenderSemanticsGestureHandler? semanticsGestureHandler = context.findRenderObject() as RenderSemanticsGestureHandler?; |
1465 | assert(() { |
1466 | if (semanticsGestureHandler == null) { |
1467 | throw FlutterError( |
1468 | 'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n' |
1469 | 'The replaceSemanticsActions() method can only be called after the RenderSemanticsGestureHandler has been created.' , |
1470 | ); |
1471 | } |
1472 | return true; |
1473 | }()); |
1474 | |
1475 | semanticsGestureHandler!.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required. |
1476 | } |
1477 | |
1478 | @override |
1479 | void dispose() { |
1480 | for (final GestureRecognizer recognizer in _recognizers!.values) { |
1481 | recognizer.dispose(); |
1482 | } |
1483 | _recognizers = null; |
1484 | super.dispose(); |
1485 | } |
1486 | |
1487 | void _syncAll(Map<Type, GestureRecognizerFactory> gestures) { |
1488 | assert(_recognizers != null); |
1489 | final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!; |
1490 | _recognizers = <Type, GestureRecognizer>{}; |
1491 | for (final Type type in gestures.keys) { |
1492 | assert(gestures[type] != null); |
1493 | assert(gestures[type]!._debugAssertTypeMatches(type)); |
1494 | assert(!_recognizers!.containsKey(type)); |
1495 | _recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor(); |
1496 | 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.' ); |
1497 | gestures[type]!.initializer(_recognizers![type]!); |
1498 | } |
1499 | for (final Type type in oldRecognizers.keys) { |
1500 | if (!_recognizers!.containsKey(type)) { |
1501 | oldRecognizers[type]!.dispose(); |
1502 | } |
1503 | } |
1504 | } |
1505 | |
1506 | void _handlePointerDown(PointerDownEvent event) { |
1507 | assert(_recognizers != null); |
1508 | for (final GestureRecognizer recognizer in _recognizers!.values) { |
1509 | recognizer.addPointer(event); |
1510 | } |
1511 | } |
1512 | |
1513 | void _handlePointerPanZoomStart(PointerPanZoomStartEvent event) { |
1514 | assert(_recognizers != null); |
1515 | for (final GestureRecognizer recognizer in _recognizers!.values) { |
1516 | recognizer.addPointerPanZoom(event); |
1517 | } |
1518 | } |
1519 | |
1520 | HitTestBehavior get _defaultBehavior { |
1521 | return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild; |
1522 | } |
1523 | |
1524 | void _updateSemanticsForRenderObject(RenderSemanticsGestureHandler renderObject) { |
1525 | assert(!widget.excludeFromSemantics); |
1526 | assert(_semantics != null); |
1527 | _semantics!.assignSemantics(renderObject); |
1528 | } |
1529 | |
1530 | @override |
1531 | Widget build(BuildContext context) { |
1532 | Widget result = Listener( |
1533 | onPointerDown: _handlePointerDown, |
1534 | onPointerPanZoomStart: _handlePointerPanZoomStart, |
1535 | behavior: widget.behavior ?? _defaultBehavior, |
1536 | child: widget.child, |
1537 | ); |
1538 | if (!widget.excludeFromSemantics) { |
1539 | result = _GestureSemantics( |
1540 | behavior: widget.behavior ?? _defaultBehavior, |
1541 | assignSemantics: _updateSemanticsForRenderObject, |
1542 | child: result, |
1543 | ); |
1544 | } |
1545 | return result; |
1546 | } |
1547 | |
1548 | @override |
1549 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1550 | super.debugFillProperties(properties); |
1551 | if (_recognizers == null) { |
1552 | properties.add(DiagnosticsNode.message('DISPOSED' )); |
1553 | } else { |
1554 | final List<String> gestures = _recognizers!.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList(); |
1555 | properties.add(IterableProperty<String>('gestures' , gestures, ifEmpty: '<none>' )); |
1556 | properties.add(IterableProperty<GestureRecognizer>('recognizers' , _recognizers!.values, level: DiagnosticLevel.fine)); |
1557 | properties.add(DiagnosticsProperty<bool>('excludeFromSemantics' , widget.excludeFromSemantics, defaultValue: false)); |
1558 | if (!widget.excludeFromSemantics) { |
1559 | properties.add(DiagnosticsProperty<SemanticsGestureDelegate>('semantics' , widget.semantics, defaultValue: null)); |
1560 | } |
1561 | } |
1562 | properties.add(EnumProperty<HitTestBehavior>('behavior' , widget.behavior, defaultValue: null)); |
1563 | } |
1564 | } |
1565 | |
1566 | typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler); |
1567 | |
1568 | class _GestureSemantics extends SingleChildRenderObjectWidget { |
1569 | const _GestureSemantics({ |
1570 | super.child, |
1571 | required this.behavior, |
1572 | required this.assignSemantics, |
1573 | }); |
1574 | |
1575 | final HitTestBehavior behavior; |
1576 | final _AssignSemantics assignSemantics; |
1577 | |
1578 | @override |
1579 | RenderSemanticsGestureHandler createRenderObject(BuildContext context) { |
1580 | final RenderSemanticsGestureHandler renderObject = RenderSemanticsGestureHandler() |
1581 | ..behavior = behavior; |
1582 | assignSemantics(renderObject); |
1583 | return renderObject; |
1584 | } |
1585 | |
1586 | @override |
1587 | void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) { |
1588 | renderObject.behavior = behavior; |
1589 | assignSemantics(renderObject); |
1590 | } |
1591 | } |
1592 | |
1593 | /// A base class that describes what semantics notations a [RawGestureDetector] |
1594 | /// should add to the render object [RenderSemanticsGestureHandler]. |
1595 | /// |
1596 | /// It is used to allow custom [GestureDetector]s to add semantics notations. |
1597 | abstract class SemanticsGestureDelegate { |
1598 | /// Create a delegate of gesture semantics. |
1599 | const SemanticsGestureDelegate(); |
1600 | |
1601 | /// Assigns semantics notations to the [RenderSemanticsGestureHandler] render |
1602 | /// object of the gesture detector. |
1603 | /// |
1604 | /// This method is called when the widget is created, updated, or during |
1605 | /// [RawGestureDetectorState.replaceGestureRecognizers]. |
1606 | void assignSemantics(RenderSemanticsGestureHandler renderObject); |
1607 | |
1608 | @override |
1609 | String toString() => ' ${objectRuntimeType(this, 'SemanticsGestureDelegate' )}()' ; |
1610 | } |
1611 | |
1612 | // The default semantics delegate of [RawGestureDetector]. Its behavior is |
1613 | // described in [RawGestureDetector.semantics]. |
1614 | // |
1615 | // For readers who come here to learn how to write custom semantics delegates: |
1616 | // this is not a proper sample code. It has access to the detector state as well |
1617 | // as its private properties, which are inaccessible normally. It is designed |
1618 | // this way in order to work independently in a [RawGestureRecognizer] to |
1619 | // preserve existing behavior. |
1620 | // |
1621 | // Instead, a normal delegate will store callbacks as properties, and use them |
1622 | // in `assignSemantics`. |
1623 | class _DefaultSemanticsGestureDelegate extends SemanticsGestureDelegate { |
1624 | _DefaultSemanticsGestureDelegate(this.detectorState); |
1625 | |
1626 | final RawGestureDetectorState detectorState; |
1627 | |
1628 | @override |
1629 | void assignSemantics(RenderSemanticsGestureHandler renderObject) { |
1630 | assert(!detectorState.widget.excludeFromSemantics); |
1631 | final Map<Type, GestureRecognizer> recognizers = detectorState._recognizers!; |
1632 | renderObject |
1633 | ..onTap = _getTapHandler(recognizers) |
1634 | ..onLongPress = _getLongPressHandler(recognizers) |
1635 | ..onHorizontalDragUpdate = _getHorizontalDragUpdateHandler(recognizers) |
1636 | ..onVerticalDragUpdate = _getVerticalDragUpdateHandler(recognizers); |
1637 | } |
1638 | |
1639 | GestureTapCallback? _getTapHandler(Map<Type, GestureRecognizer> recognizers) { |
1640 | final TapGestureRecognizer? tap = recognizers[TapGestureRecognizer] as TapGestureRecognizer?; |
1641 | if (tap == null) { |
1642 | return null; |
1643 | } |
1644 | |
1645 | return () { |
1646 | tap.onTapDown?.call(TapDownDetails()); |
1647 | tap.onTapUp?.call(TapUpDetails(kind: PointerDeviceKind.unknown)); |
1648 | tap.onTap?.call(); |
1649 | }; |
1650 | } |
1651 | |
1652 | GestureLongPressCallback? _getLongPressHandler(Map<Type, GestureRecognizer> recognizers) { |
1653 | final LongPressGestureRecognizer? longPress = recognizers[LongPressGestureRecognizer] as LongPressGestureRecognizer?; |
1654 | if (longPress == null) { |
1655 | return null; |
1656 | } |
1657 | |
1658 | return () { |
1659 | longPress.onLongPressDown?.call(const LongPressDownDetails()); |
1660 | longPress.onLongPressStart?.call(const LongPressStartDetails()); |
1661 | longPress.onLongPress?.call(); |
1662 | longPress.onLongPressEnd?.call(const LongPressEndDetails()); |
1663 | longPress.onLongPressUp?.call(); |
1664 | }; |
1665 | } |
1666 | |
1667 | GestureDragUpdateCallback? _getHorizontalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) { |
1668 | final HorizontalDragGestureRecognizer? horizontal = recognizers[HorizontalDragGestureRecognizer] as HorizontalDragGestureRecognizer?; |
1669 | final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?; |
1670 | |
1671 | final GestureDragUpdateCallback? horizontalHandler = horizontal == null ? |
1672 | null : |
1673 | (DragUpdateDetails details) { |
1674 | horizontal.onDown?.call(DragDownDetails()); |
1675 | horizontal.onStart?.call(DragStartDetails()); |
1676 | horizontal.onUpdate?.call(details); |
1677 | horizontal.onEnd?.call(DragEndDetails(primaryVelocity: 0.0)); |
1678 | }; |
1679 | |
1680 | final GestureDragUpdateCallback? panHandler = pan == null ? |
1681 | null : |
1682 | (DragUpdateDetails details) { |
1683 | pan.onDown?.call(DragDownDetails()); |
1684 | pan.onStart?.call(DragStartDetails()); |
1685 | pan.onUpdate?.call(details); |
1686 | pan.onEnd?.call(DragEndDetails()); |
1687 | }; |
1688 | |
1689 | if (horizontalHandler == null && panHandler == null) { |
1690 | return null; |
1691 | } |
1692 | return (DragUpdateDetails details) { |
1693 | if (horizontalHandler != null) { |
1694 | horizontalHandler(details); |
1695 | } |
1696 | if (panHandler != null) { |
1697 | panHandler(details); |
1698 | } |
1699 | }; |
1700 | } |
1701 | |
1702 | GestureDragUpdateCallback? _getVerticalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) { |
1703 | final VerticalDragGestureRecognizer? vertical = recognizers[VerticalDragGestureRecognizer] as VerticalDragGestureRecognizer?; |
1704 | final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?; |
1705 | |
1706 | final GestureDragUpdateCallback? verticalHandler = vertical == null ? |
1707 | null : |
1708 | (DragUpdateDetails details) { |
1709 | vertical.onDown?.call(DragDownDetails()); |
1710 | vertical.onStart?.call(DragStartDetails()); |
1711 | vertical.onUpdate?.call(details); |
1712 | vertical.onEnd?.call(DragEndDetails(primaryVelocity: 0.0)); |
1713 | }; |
1714 | |
1715 | final GestureDragUpdateCallback? panHandler = pan == null ? |
1716 | null : |
1717 | (DragUpdateDetails details) { |
1718 | pan.onDown?.call(DragDownDetails()); |
1719 | pan.onStart?.call(DragStartDetails()); |
1720 | pan.onUpdate?.call(details); |
1721 | pan.onEnd?.call(DragEndDetails()); |
1722 | }; |
1723 | |
1724 | if (verticalHandler == null && panHandler == null) { |
1725 | return null; |
1726 | } |
1727 | return (DragUpdateDetails details) { |
1728 | if (verticalHandler != null) { |
1729 | verticalHandler(details); |
1730 | } |
1731 | if (panHandler != null) { |
1732 | panHandler(details); |
1733 | } |
1734 | }; |
1735 | } |
1736 | } |
1737 | |