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
5import 'package:flutter/foundation.dart';
6import 'package:flutter/gestures.dart';
7import 'package:flutter/rendering.dart';
8
9import 'basic.dart';
10import 'framework.dart';
11import 'media_query.dart';
12
13export '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;
49export '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
63abstract 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].
84typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function();
85
86/// Signature for closures that implement [GestureRecognizerFactory.initializer].
87typedef 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].
92class 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.
210class 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.
1278class 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].
1393class 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
1566typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler);
1567
1568class _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.
1597abstract 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`.
1623class _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