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