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] = GestureRecognizerFactoryWithHandlers<
1184 HorizontalDragGestureRecognizer
1185 >(
1186 () => HorizontalDragGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
1187 (HorizontalDragGestureRecognizer instance) {
1188 instance
1189 ..onDown = onHorizontalDragDown
1190 ..onStart = onHorizontalDragStart
1191 ..onUpdate = onHorizontalDragUpdate
1192 ..onEnd = onHorizontalDragEnd
1193 ..onCancel = onHorizontalDragCancel
1194 ..dragStartBehavior = dragStartBehavior
1195 ..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context)
1196 ..gestureSettings = gestureSettings
1197 ..supportedDevices = supportedDevices;
1198 },
1199 );
1200 }
1201
1202 if (onPanDown != null ||
1203 onPanStart != null ||
1204 onPanUpdate != null ||
1205 onPanEnd != null ||
1206 onPanCancel != null) {
1207 gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
1208 () => PanGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
1209 (PanGestureRecognizer instance) {
1210 instance
1211 ..onDown = onPanDown
1212 ..onStart = onPanStart
1213 ..onUpdate = onPanUpdate
1214 ..onEnd = onPanEnd
1215 ..onCancel = onPanCancel
1216 ..dragStartBehavior = dragStartBehavior
1217 ..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context)
1218 ..gestureSettings = gestureSettings
1219 ..supportedDevices = supportedDevices;
1220 },
1221 );
1222 }
1223
1224 if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
1225 gestures[ScaleGestureRecognizer] =
1226 GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
1227 () => ScaleGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
1228 (ScaleGestureRecognizer instance) {
1229 instance
1230 ..onStart = onScaleStart
1231 ..onUpdate = onScaleUpdate
1232 ..onEnd = onScaleEnd
1233 ..dragStartBehavior = dragStartBehavior
1234 ..gestureSettings = gestureSettings
1235 ..trackpadScrollCausesScale = trackpadScrollCausesScale
1236 ..trackpadScrollToScaleFactor = trackpadScrollToScaleFactor
1237 ..supportedDevices = supportedDevices;
1238 },
1239 );
1240 }
1241
1242 if (onForcePressStart != null ||
1243 onForcePressPeak != null ||
1244 onForcePressUpdate != null ||
1245 onForcePressEnd != null) {
1246 gestures[ForcePressGestureRecognizer] =
1247 GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
1248 () => ForcePressGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
1249 (ForcePressGestureRecognizer instance) {
1250 instance
1251 ..onStart = onForcePressStart
1252 ..onPeak = onForcePressPeak
1253 ..onUpdate = onForcePressUpdate
1254 ..onEnd = onForcePressEnd
1255 ..gestureSettings = gestureSettings
1256 ..supportedDevices = supportedDevices;
1257 },
1258 );
1259 }
1260
1261 return RawGestureDetector(
1262 gestures: gestures,
1263 behavior: behavior,
1264 excludeFromSemantics: excludeFromSemantics,
1265 child: child,
1266 );
1267 }
1268
1269 @override
1270 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1271 super.debugFillProperties(properties);
1272 properties.add(EnumProperty<DragStartBehavior>('startBehavior', dragStartBehavior));
1273 }
1274}
1275
1276/// A widget that detects gestures described by the given gesture
1277/// factories.
1278///
1279/// For common gestures, use a [GestureDetector].
1280/// [RawGestureDetector] is useful primarily when developing your
1281/// own gesture recognizers.
1282///
1283/// Configuring the gesture recognizers requires a carefully constructed map, as
1284/// described in [gestures] and as shown in the example below.
1285///
1286/// {@tool snippet}
1287///
1288/// This example shows how to hook up a [TapGestureRecognizer]. It assumes that
1289/// the code is being used inside a [State] object with a `_last` field that is
1290/// then displayed as the child of the gesture detector.
1291///
1292/// ```dart
1293/// RawGestureDetector(
1294/// gestures: <Type, GestureRecognizerFactory>{
1295/// TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
1296/// () => TapGestureRecognizer(),
1297/// (TapGestureRecognizer instance) {
1298/// instance
1299/// ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
1300/// ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
1301/// ..onTap = () { setState(() { _last = 'tap'; }); }
1302/// ..onTapCancel = () { setState(() { _last = 'cancel'; }); };
1303/// },
1304/// ),
1305/// },
1306/// child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)),
1307/// )
1308/// ```
1309/// {@end-tool}
1310///
1311/// See also:
1312///
1313/// * [GestureDetector], a less flexible but much simpler widget that does the same thing.
1314/// * [Listener], a widget that reports raw pointer events.
1315/// * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
1316class RawGestureDetector extends StatefulWidget {
1317 /// Creates a widget that detects gestures.
1318 ///
1319 /// Gesture detectors can contribute semantic information to the tree that is
1320 /// used by assistive technology. The behavior can be configured by
1321 /// [semantics], or disabled with [excludeFromSemantics].
1322 const RawGestureDetector({
1323 super.key,
1324 this.child,
1325 this.gestures = const <Type, GestureRecognizerFactory>{},
1326 this.behavior,
1327 this.excludeFromSemantics = false,
1328 this.semantics,
1329 });
1330
1331 /// The widget below this widget in the tree.
1332 ///
1333 /// {@macro flutter.widgets.ProxyWidget.child}
1334 final Widget? child;
1335
1336 /// The gestures that this widget will attempt to recognize.
1337 ///
1338 /// This should be a map from [GestureRecognizer] subclasses to
1339 /// [GestureRecognizerFactory] subclasses specialized with the same type.
1340 ///
1341 /// This value can be late-bound at layout time using
1342 /// [RawGestureDetectorState.replaceGestureRecognizers].
1343 final Map<Type, GestureRecognizerFactory> gestures;
1344
1345 /// How this gesture detector should behave during hit testing.
1346 ///
1347 /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
1348 /// [HitTestBehavior.translucent] if child is null.
1349 final HitTestBehavior? behavior;
1350
1351 /// Whether to exclude these gestures from the semantics tree. For
1352 /// example, the long-press gesture for showing a tooltip is
1353 /// excluded because the tooltip itself is included in the semantics
1354 /// tree directly and so having a gesture to show it would result in
1355 /// duplication of information.
1356 final bool excludeFromSemantics;
1357
1358 /// Describes the semantics notations that should be added to the underlying
1359 /// render object [RenderSemanticsGestureHandler].
1360 ///
1361 /// It has no effect if [excludeFromSemantics] is true.
1362 ///
1363 /// When [semantics] is null, [RawGestureDetector] will fall back to a
1364 /// default delegate which checks if the detector owns certain gesture
1365 /// recognizers and calls their callbacks if they exist:
1366 ///
1367 /// * During a semantic tap, it calls [TapGestureRecognizer]'s
1368 /// `onTapDown`, `onTapUp`, and `onTap`.
1369 /// * During a semantic long press, it calls [LongPressGestureRecognizer]'s
1370 /// `onLongPressDown`, `onLongPressStart`, `onLongPress`, `onLongPressEnd`
1371 /// and `onLongPressUp`.
1372 /// * During a semantic horizontal drag, it calls [HorizontalDragGestureRecognizer]'s
1373 /// `onDown`, `onStart`, `onUpdate` and `onEnd`, then
1374 /// [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`.
1375 /// * During a semantic vertical drag, it calls [VerticalDragGestureRecognizer]'s
1376 /// `onDown`, `onStart`, `onUpdate` and `onEnd`, then
1377 /// [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`.
1378 ///
1379 /// {@tool snippet}
1380 /// This custom gesture detector listens to force presses, while also allows
1381 /// the same callback to be triggered by semantic long presses.
1382 ///
1383 /// ```dart
1384 /// class ForcePressGestureDetectorWithSemantics extends StatelessWidget {
1385 /// const ForcePressGestureDetectorWithSemantics({
1386 /// super.key,
1387 /// required this.child,
1388 /// required this.onForcePress,
1389 /// });
1390 ///
1391 /// final Widget child;
1392 /// final VoidCallback onForcePress;
1393 ///
1394 /// @override
1395 /// Widget build(BuildContext context) {
1396 /// return RawGestureDetector(
1397 /// gestures: <Type, GestureRecognizerFactory>{
1398 /// ForcePressGestureRecognizer: GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
1399 /// () => ForcePressGestureRecognizer(debugOwner: this),
1400 /// (ForcePressGestureRecognizer instance) {
1401 /// instance.onStart = (_) => onForcePress();
1402 /// }
1403 /// ),
1404 /// },
1405 /// behavior: HitTestBehavior.opaque,
1406 /// semantics: _LongPressSemanticsDelegate(onForcePress),
1407 /// child: child,
1408 /// );
1409 /// }
1410 /// }
1411 ///
1412 /// class _LongPressSemanticsDelegate extends SemanticsGestureDelegate {
1413 /// _LongPressSemanticsDelegate(this.onLongPress);
1414 ///
1415 /// VoidCallback onLongPress;
1416 ///
1417 /// @override
1418 /// void assignSemantics(RenderSemanticsGestureHandler renderObject) {
1419 /// renderObject.onLongPress = onLongPress;
1420 /// }
1421 /// }
1422 /// ```
1423 /// {@end-tool}
1424 final SemanticsGestureDelegate? semantics;
1425
1426 @override
1427 RawGestureDetectorState createState() => RawGestureDetectorState();
1428}
1429
1430/// State for a [RawGestureDetector].
1431class RawGestureDetectorState extends State<RawGestureDetector> {
1432 Map<Type, GestureRecognizer>? _recognizers = const <Type, GestureRecognizer>{};
1433 SemanticsGestureDelegate? _semantics;
1434
1435 @protected
1436 @override
1437 void initState() {
1438 super.initState();
1439 _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
1440 _syncAll(widget.gestures);
1441 }
1442
1443 @protected
1444 @override
1445 void didUpdateWidget(RawGestureDetector oldWidget) {
1446 super.didUpdateWidget(oldWidget);
1447 if (!(oldWidget.semantics == null && widget.semantics == null)) {
1448 _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
1449 }
1450 _syncAll(widget.gestures);
1451 }
1452
1453 /// This method can be called after the build phase, during the
1454 /// layout of the nearest descendant [RenderObjectWidget] of the
1455 /// gesture detector, to update the list of active gesture
1456 /// recognizers.
1457 ///
1458 /// The typical use case is [Scrollable]s, which put their viewport
1459 /// in their gesture detector, and then need to know the dimensions
1460 /// of the viewport and the viewport's child to determine whether
1461 /// the gesture detector should be enabled.
1462 ///
1463 /// The argument should follow the same conventions as
1464 /// [RawGestureDetector.gestures]. It acts like a temporary replacement for
1465 /// that value until the next build.
1466 void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
1467 assert(() {
1468 if (!context.findRenderObject()!.owner!.debugDoingLayout) {
1469 throw FlutterError.fromParts(<DiagnosticsNode>[
1470 ErrorSummary(
1471 'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.',
1472 ),
1473 ErrorDescription(
1474 'The replaceGestureRecognizers() method can only be called during the layout phase.',
1475 ),
1476 ErrorHint(
1477 'To set the gesture recognizers at other times, trigger a new build using setState() '
1478 'and provide the new gesture recognizers as constructor arguments to the corresponding '
1479 'RawGestureDetector or GestureDetector object.',
1480 ),
1481 ]);
1482 }
1483 return true;
1484 }());
1485 _syncAll(gestures);
1486 if (!widget.excludeFromSemantics) {
1487 final RenderSemanticsGestureHandler semanticsGestureHandler =
1488 context.findRenderObject()! as RenderSemanticsGestureHandler;
1489 _updateSemanticsForRenderObject(semanticsGestureHandler);
1490 }
1491 }
1492
1493 /// This method can be called to filter the list of available semantic actions,
1494 /// after the render object was created.
1495 ///
1496 /// The actual filtering is happening in the next frame and a frame will be
1497 /// scheduled if non is pending.
1498 ///
1499 /// This is used by [Scrollable] to configure system accessibility tools so
1500 /// that they know in which direction a particular list can be scrolled.
1501 ///
1502 /// If this is never called, then the actions are not filtered. If the list of
1503 /// actions to filter changes, it must be called again.
1504 void replaceSemanticsActions(Set<SemanticsAction> actions) {
1505 if (widget.excludeFromSemantics) {
1506 return;
1507 }
1508
1509 final RenderSemanticsGestureHandler? semanticsGestureHandler =
1510 context.findRenderObject() as RenderSemanticsGestureHandler?;
1511 assert(() {
1512 if (semanticsGestureHandler == null) {
1513 throw FlutterError(
1514 'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
1515 'The replaceSemanticsActions() method can only be called after the RenderSemanticsGestureHandler has been created.',
1516 );
1517 }
1518 return true;
1519 }());
1520
1521 semanticsGestureHandler!.validActions =
1522 actions; // will call _markNeedsSemanticsUpdate(), if required.
1523 }
1524
1525 @protected
1526 @override
1527 void dispose() {
1528 for (final GestureRecognizer recognizer in _recognizers!.values) {
1529 recognizer.dispose();
1530 }
1531 _recognizers = null;
1532 super.dispose();
1533 }
1534
1535 void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
1536 assert(_recognizers != null);
1537 final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
1538 _recognizers = <Type, GestureRecognizer>{};
1539 for (final Type type in gestures.keys) {
1540 assert(gestures[type] != null);
1541 assert(gestures[type]!._debugAssertTypeMatches(type));
1542 assert(!_recognizers!.containsKey(type));
1543 _recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
1544 assert(
1545 _recognizers![type].runtimeType == type,
1546 '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.',
1547 );
1548 gestures[type]!.initializer(_recognizers![type]!);
1549 }
1550 for (final Type type in oldRecognizers.keys) {
1551 if (!_recognizers!.containsKey(type)) {
1552 oldRecognizers[type]!.dispose();
1553 }
1554 }
1555 }
1556
1557 void _handlePointerDown(PointerDownEvent event) {
1558 assert(_recognizers != null);
1559 for (final GestureRecognizer recognizer in _recognizers!.values) {
1560 recognizer.addPointer(event);
1561 }
1562 }
1563
1564 void _handlePointerPanZoomStart(PointerPanZoomStartEvent event) {
1565 assert(_recognizers != null);
1566 for (final GestureRecognizer recognizer in _recognizers!.values) {
1567 recognizer.addPointerPanZoom(event);
1568 }
1569 }
1570
1571 HitTestBehavior get _defaultBehavior {
1572 return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
1573 }
1574
1575 void _updateSemanticsForRenderObject(RenderSemanticsGestureHandler renderObject) {
1576 assert(!widget.excludeFromSemantics);
1577 assert(_semantics != null);
1578 _semantics!.assignSemantics(renderObject);
1579 }
1580
1581 @protected
1582 @override
1583 Widget build(BuildContext context) {
1584 Widget result = Listener(
1585 onPointerDown: _handlePointerDown,
1586 onPointerPanZoomStart: _handlePointerPanZoomStart,
1587 behavior: widget.behavior ?? _defaultBehavior,
1588 child: widget.child,
1589 );
1590 if (!widget.excludeFromSemantics) {
1591 result = _GestureSemantics(
1592 behavior: widget.behavior ?? _defaultBehavior,
1593 assignSemantics: _updateSemanticsForRenderObject,
1594 child: result,
1595 );
1596 }
1597 return result;
1598 }
1599
1600 @protected
1601 @override
1602 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1603 super.debugFillProperties(properties);
1604 if (_recognizers == null) {
1605 properties.add(DiagnosticsNode.message('DISPOSED'));
1606 } else {
1607 final List<String> gestures =
1608 _recognizers!.values
1609 .map<String>((GestureRecognizer recognizer) => recognizer.debugDescription)
1610 .toList();
1611 properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
1612 properties.add(
1613 IterableProperty<GestureRecognizer>(
1614 'recognizers',
1615 _recognizers!.values,
1616 level: DiagnosticLevel.fine,
1617 ),
1618 );
1619 properties.add(
1620 DiagnosticsProperty<bool>(
1621 'excludeFromSemantics',
1622 widget.excludeFromSemantics,
1623 defaultValue: false,
1624 ),
1625 );
1626 if (!widget.excludeFromSemantics) {
1627 properties.add(
1628 DiagnosticsProperty<SemanticsGestureDelegate>(
1629 'semantics',
1630 widget.semantics,
1631 defaultValue: null,
1632 ),
1633 );
1634 }
1635 }
1636 properties.add(EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
1637 }
1638}
1639
1640typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler);
1641
1642class _GestureSemantics extends SingleChildRenderObjectWidget {
1643 const _GestureSemantics({super.child, required this.behavior, required this.assignSemantics});
1644
1645 final HitTestBehavior behavior;
1646 final _AssignSemantics assignSemantics;
1647
1648 @override
1649 RenderSemanticsGestureHandler createRenderObject(BuildContext context) {
1650 final RenderSemanticsGestureHandler renderObject =
1651 RenderSemanticsGestureHandler()..behavior = behavior;
1652 assignSemantics(renderObject);
1653 return renderObject;
1654 }
1655
1656 @override
1657 void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
1658 renderObject.behavior = behavior;
1659 assignSemantics(renderObject);
1660 }
1661}
1662
1663/// A base class that describes what semantics notations a [RawGestureDetector]
1664/// should add to the render object [RenderSemanticsGestureHandler].
1665///
1666/// It is used to allow custom [GestureDetector]s to add semantics notations.
1667abstract class SemanticsGestureDelegate {
1668 /// Create a delegate of gesture semantics.
1669 const SemanticsGestureDelegate();
1670
1671 /// Assigns semantics notations to the [RenderSemanticsGestureHandler] render
1672 /// object of the gesture detector.
1673 ///
1674 /// This method is called when the widget is created, updated, or during
1675 /// [RawGestureDetectorState.replaceGestureRecognizers].
1676 void assignSemantics(RenderSemanticsGestureHandler renderObject);
1677
1678 @override
1679 String toString() => '${objectRuntimeType(this, 'SemanticsGestureDelegate')}()';
1680}
1681
1682// The default semantics delegate of [RawGestureDetector]. Its behavior is
1683// described in [RawGestureDetector.semantics].
1684//
1685// For readers who come here to learn how to write custom semantics delegates:
1686// this is not a proper sample code. It has access to the detector state as well
1687// as its private properties, which are inaccessible normally. It is designed
1688// this way in order to work independently in a [RawGestureRecognizer] to
1689// preserve existing behavior.
1690//
1691// Instead, a normal delegate will store callbacks as properties, and use them
1692// in `assignSemantics`.
1693class _DefaultSemanticsGestureDelegate extends SemanticsGestureDelegate {
1694 _DefaultSemanticsGestureDelegate(this.detectorState);
1695
1696 final RawGestureDetectorState detectorState;
1697
1698 @override
1699 void assignSemantics(RenderSemanticsGestureHandler renderObject) {
1700 assert(!detectorState.widget.excludeFromSemantics);
1701 final Map<Type, GestureRecognizer> recognizers = detectorState._recognizers!;
1702 renderObject
1703 ..onTap = _getTapHandler(recognizers)
1704 ..onLongPress = _getLongPressHandler(recognizers)
1705 ..onHorizontalDragUpdate = _getHorizontalDragUpdateHandler(recognizers)
1706 ..onVerticalDragUpdate = _getVerticalDragUpdateHandler(recognizers);
1707 }
1708
1709 GestureTapCallback? _getTapHandler(Map<Type, GestureRecognizer> recognizers) {
1710 final TapGestureRecognizer? tap = recognizers[TapGestureRecognizer] as TapGestureRecognizer?;
1711 if (tap == null) {
1712 return null;
1713 }
1714
1715 return () {
1716 tap.onTapDown?.call(TapDownDetails());
1717 tap.onTapUp?.call(TapUpDetails(kind: PointerDeviceKind.unknown));
1718 tap.onTap?.call();
1719 };
1720 }
1721
1722 GestureLongPressCallback? _getLongPressHandler(Map<Type, GestureRecognizer> recognizers) {
1723 final LongPressGestureRecognizer? longPress =
1724 recognizers[LongPressGestureRecognizer] as LongPressGestureRecognizer?;
1725 if (longPress == null) {
1726 return null;
1727 }
1728
1729 return () {
1730 longPress.onLongPressDown?.call(const LongPressDownDetails());
1731 longPress.onLongPressStart?.call(const LongPressStartDetails());
1732 longPress.onLongPress?.call();
1733 longPress.onLongPressEnd?.call(const LongPressEndDetails());
1734 longPress.onLongPressUp?.call();
1735 };
1736 }
1737
1738 GestureDragUpdateCallback? _getHorizontalDragUpdateHandler(
1739 Map<Type, GestureRecognizer> recognizers,
1740 ) {
1741 final HorizontalDragGestureRecognizer? horizontal =
1742 recognizers[HorizontalDragGestureRecognizer] as HorizontalDragGestureRecognizer?;
1743 final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?;
1744
1745 final GestureDragUpdateCallback? horizontalHandler =
1746 horizontal == null
1747 ? null
1748 : (DragUpdateDetails details) {
1749 horizontal.onDown?.call(DragDownDetails());
1750 horizontal.onStart?.call(DragStartDetails());
1751 horizontal.onUpdate?.call(details);
1752 horizontal.onEnd?.call(DragEndDetails(primaryVelocity: 0.0));
1753 };
1754
1755 final GestureDragUpdateCallback? panHandler =
1756 pan == null
1757 ? null
1758 : (DragUpdateDetails details) {
1759 pan.onDown?.call(DragDownDetails());
1760 pan.onStart?.call(DragStartDetails());
1761 pan.onUpdate?.call(details);
1762 pan.onEnd?.call(DragEndDetails());
1763 };
1764
1765 if (horizontalHandler == null && panHandler == null) {
1766 return null;
1767 }
1768 return (DragUpdateDetails details) {
1769 horizontalHandler?.call(details);
1770 panHandler?.call(details);
1771 };
1772 }
1773
1774 GestureDragUpdateCallback? _getVerticalDragUpdateHandler(
1775 Map<Type, GestureRecognizer> recognizers,
1776 ) {
1777 final VerticalDragGestureRecognizer? vertical =
1778 recognizers[VerticalDragGestureRecognizer] as VerticalDragGestureRecognizer?;
1779 final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?;
1780
1781 final GestureDragUpdateCallback? verticalHandler =
1782 vertical == null
1783 ? null
1784 : (DragUpdateDetails details) {
1785 vertical.onDown?.call(DragDownDetails());
1786 vertical.onStart?.call(DragStartDetails());
1787 vertical.onUpdate?.call(details);
1788 vertical.onEnd?.call(DragEndDetails(primaryVelocity: 0.0));
1789 };
1790
1791 final GestureDragUpdateCallback? panHandler =
1792 pan == null
1793 ? null
1794 : (DragUpdateDetails details) {
1795 pan.onDown?.call(DragDownDetails());
1796 pan.onStart?.call(DragStartDetails());
1797 pan.onUpdate?.call(details);
1798 pan.onEnd?.call(DragEndDetails());
1799 };
1800
1801 if (verticalHandler == null && panHandler == null) {
1802 return null;
1803 }
1804 return (DragUpdateDetails details) {
1805 verticalHandler?.call(details);
1806 panHandler?.call(details);
1807 };
1808 }
1809}
1810

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com