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