import 'package:flutter/widgets.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; // as of Flutter v3.27.1, `GestureDetector` does not allow setting long press delay // adapted from Flutter `GestureDetector` in `/widgets/gesture_detector.dart` class AGestureDetector extends StatelessWidget { /// Creates a widget that detects gestures. /// /// Pan and scale callbacks cannot be used simultaneously because scale is a /// superset of pan. Use the scale callbacks instead. /// /// Horizontal and vertical drag callbacks cannot be used simultaneously /// because a combination of a horizontal and vertical drag is a pan. /// Use the pan callbacks instead. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=WhVXkCFPmK4} /// /// By default, gesture detectors contribute semantic information to the tree /// that is used by assistive technology. AGestureDetector({ super.key, this.child, this.onTapDown, this.onTapUp, this.onTap, this.onTapCancel, this.onSecondaryTap, this.onSecondaryTapDown, this.onSecondaryTapUp, this.onSecondaryTapCancel, this.onTertiaryTapDown, this.onTertiaryTapUp, this.onTertiaryTapCancel, this.onDoubleTapDown, this.onDoubleTap, this.onDoubleTapCancel, this.onLongPressDown, this.onLongPressCancel, this.onLongPress, this.onLongPressStart, this.onLongPressMoveUpdate, this.onLongPressUp, this.onLongPressEnd, this.onSecondaryLongPressDown, this.onSecondaryLongPressCancel, this.onSecondaryLongPress, this.onSecondaryLongPressStart, this.onSecondaryLongPressMoveUpdate, this.onSecondaryLongPressUp, this.onSecondaryLongPressEnd, this.onTertiaryLongPressDown, this.onTertiaryLongPressCancel, this.onTertiaryLongPress, this.onTertiaryLongPressStart, this.onTertiaryLongPressMoveUpdate, this.onTertiaryLongPressUp, this.onTertiaryLongPressEnd, this.onVerticalDragDown, this.onVerticalDragStart, this.onVerticalDragUpdate, this.onVerticalDragEnd, this.onVerticalDragCancel, this.onHorizontalDragDown, this.onHorizontalDragStart, this.onHorizontalDragUpdate, this.onHorizontalDragEnd, this.onHorizontalDragCancel, this.onForcePressStart, this.onForcePressPeak, this.onForcePressUpdate, this.onForcePressEnd, this.onPanDown, this.onPanStart, this.onPanUpdate, this.onPanEnd, this.onPanCancel, this.onScaleStart, this.onScaleUpdate, this.onScaleEnd, this.behavior, this.excludeFromSemantics = false, this.dragStartBehavior = DragStartBehavior.start, this.trackpadScrollCausesScale = false, this.trackpadScrollToScaleFactor = kDefaultTrackpadScrollToScaleFactor, this.supportedDevices, this.longPressTimeout = kLongPressTimeout, }) : assert(() { final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null; final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null; final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null; final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null; if (havePan || haveScale) { if (havePan && haveScale) { throw FlutterError.fromParts([ ErrorSummary('Incorrect GestureDetector arguments.'), ErrorDescription( 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.', ), ErrorHint('Just use the scale gesture recognizer.'), ]); } final String recognizer = havePan ? 'pan' : 'scale'; if (haveVerticalDrag && haveHorizontalDrag) { throw FlutterError( 'Incorrect GestureDetector arguments.\n' 'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer ' 'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.', ); } } return true; }()); /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.ProxyWidget.child} final Widget? child; /// A pointer that might cause a tap with a primary button has contacted the /// screen at a particular location. /// /// This is called after a short timeout, even if the winning gesture has not /// yet been selected. If the tap gesture wins, [onTapUp] will be called, /// otherwise [onTapCancel] will be called. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureTapDownCallback? onTapDown; /// A pointer that will trigger a tap with a primary button has stopped /// contacting the screen at a particular location. /// /// This triggers immediately before [onTap] in the case of the tap gesture /// winning. If the tap gesture did not win, [onTapCancel] is called instead. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureTapUpCallback? onTapUp; /// A tap with a primary button has occurred. /// /// This triggers when the tap gesture wins. If the tap gesture did not win, /// [onTapCancel] is called instead. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. /// * [onTapUp], which is called at the same time but includes details /// regarding the pointer position. final GestureTapCallback? onTap; /// The pointer that previously triggered [onTapDown] will not end up causing /// a tap. /// /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if /// the tap gesture did not win. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureTapCancelCallback? onTapCancel; /// A tap with a secondary button has occurred. /// /// This triggers when the tap gesture wins. If the tap gesture did not win, /// [onSecondaryTapCancel] is called instead. /// /// See also: /// /// * [kSecondaryButton], the button this callback responds to. /// * [onSecondaryTapUp], which is called at the same time but includes details /// regarding the pointer position. final GestureTapCallback? onSecondaryTap; /// A pointer that might cause a tap with a secondary button has contacted the /// screen at a particular location. /// /// This is called after a short timeout, even if the winning gesture has not /// yet been selected. If the tap gesture wins, [onSecondaryTapUp] will be /// called, otherwise [onSecondaryTapCancel] will be called. /// /// See also: /// /// * [kSecondaryButton], the button this callback responds to. final GestureTapDownCallback? onSecondaryTapDown; /// A pointer that will trigger a tap with a secondary button has stopped /// contacting the screen at a particular location. /// /// This triggers in the case of the tap gesture winning. If the tap gesture /// did not win, [onSecondaryTapCancel] is called instead. /// /// See also: /// /// * [onSecondaryTap], a handler triggered right after this one that doesn't /// pass any details about the tap. /// * [kSecondaryButton], the button this callback responds to. final GestureTapUpCallback? onSecondaryTapUp; /// The pointer that previously triggered [onSecondaryTapDown] will not end up /// causing a tap. /// /// This is called after [onSecondaryTapDown], and instead of /// [onSecondaryTapUp], if the tap gesture did not win. /// /// See also: /// /// * [kSecondaryButton], the button this callback responds to. final GestureTapCancelCallback? onSecondaryTapCancel; /// A pointer that might cause a tap with a tertiary button has contacted the /// screen at a particular location. /// /// This is called after a short timeout, even if the winning gesture has not /// yet been selected. If the tap gesture wins, [onTertiaryTapUp] will be /// called, otherwise [onTertiaryTapCancel] will be called. /// /// See also: /// /// * [kTertiaryButton], the button this callback responds to. final GestureTapDownCallback? onTertiaryTapDown; /// A pointer that will trigger a tap with a tertiary button has stopped /// contacting the screen at a particular location. /// /// This triggers in the case of the tap gesture winning. If the tap gesture /// did not win, [onTertiaryTapCancel] is called instead. /// /// See also: /// /// * [kTertiaryButton], the button this callback responds to. final GestureTapUpCallback? onTertiaryTapUp; /// The pointer that previously triggered [onTertiaryTapDown] will not end up /// causing a tap. /// /// This is called after [onTertiaryTapDown], and instead of /// [onTertiaryTapUp], if the tap gesture did not win. /// /// See also: /// /// * [kTertiaryButton], the button this callback responds to. final GestureTapCancelCallback? onTertiaryTapCancel; /// A pointer that might cause a double tap has contacted the screen at a /// particular location. /// /// Triggered immediately after the down event of the second tap. /// /// If the user completes the double tap and the gesture wins, [onDoubleTap] /// will be called after this callback. Otherwise, [onDoubleTapCancel] will /// be called after this callback. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureTapDownCallback? onDoubleTapDown; /// The user has tapped the screen with a primary button at the same location /// twice in quick succession. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureTapCallback? onDoubleTap; /// The pointer that previously triggered [onDoubleTapDown] will not end up /// causing a double tap. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureTapCancelCallback? onDoubleTapCancel; /// The pointer has contacted the screen with a primary button, which might /// be the start of a long-press. /// /// This triggers after the pointer down event. /// /// If the user completes the long-press, and this gesture wins, /// [onLongPressStart] will be called after this callback. Otherwise, /// [onLongPressCancel] will be called after this callback. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. /// * [onSecondaryLongPressDown], a similar callback but for a secondary button. /// * [onTertiaryLongPressDown], a similar callback but for a tertiary button. /// * [LongPressGestureRecognizer.onLongPressDown], which exposes this /// callback at the gesture layer. final GestureLongPressDownCallback? onLongPressDown; /// A pointer that previously triggered [onLongPressDown] will not end up /// causing a long-press. /// /// This triggers once the gesture loses if [onLongPressDown] has previously /// been triggered. /// /// If the user completed the long-press, and the gesture won, then /// [onLongPressStart] and [onLongPress] are called instead. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onLongPressCancel], which exposes this /// callback at the gesture layer. final GestureLongPressCancelCallback? onLongPressCancel; /// Called when a long press gesture with a primary button has been recognized. /// /// Triggered when a pointer has remained in contact with the screen at the /// same location for a long period of time. /// /// This is equivalent to (and is called immediately after) [onLongPressStart]. /// The only difference between the two is that this callback does not /// contain details of the position at which the pointer initially contacted /// the screen. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onLongPress], which exposes this /// callback at the gesture layer. final GestureLongPressCallback? onLongPress; /// Called when a long press gesture with a primary button has been recognized. /// /// Triggered when a pointer has remained in contact with the screen at the /// same location for a long period of time. /// /// This is equivalent to (and is called immediately before) [onLongPress]. /// The only difference between the two is that this callback contains /// details of the position at which the pointer initially contacted the /// screen, whereas [onLongPress] does not. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onLongPressStart], which exposes this /// callback at the gesture layer. final GestureLongPressStartCallback? onLongPressStart; /// A pointer has been drag-moved after a long-press with a primary button. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onLongPressMoveUpdate], which exposes this /// callback at the gesture layer. final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate; /// A pointer that has triggered a long-press with a primary button has /// stopped contacting the screen. /// /// This is equivalent to (and is called immediately after) [onLongPressEnd]. /// The only difference between the two is that this callback does not /// contain details of the state of the pointer when it stopped contacting /// the screen. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onLongPressUp], which exposes this /// callback at the gesture layer. final GestureLongPressUpCallback? onLongPressUp; /// A pointer that has triggered a long-press with a primary button has /// stopped contacting the screen. /// /// This is equivalent to (and is called immediately before) [onLongPressUp]. /// The only difference between the two is that this callback contains /// details of the state of the pointer when it stopped contacting the /// screen, whereas [onLongPressUp] does not. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onLongPressEnd], which exposes this /// callback at the gesture layer. final GestureLongPressEndCallback? onLongPressEnd; /// The pointer has contacted the screen with a secondary button, which might /// be the start of a long-press. /// /// This triggers after the pointer down event. /// /// If the user completes the long-press, and this gesture wins, /// [onSecondaryLongPressStart] will be called after this callback. Otherwise, /// [onSecondaryLongPressCancel] will be called after this callback. /// /// See also: /// /// * [kSecondaryButton], the button this callback responds to. /// * [onLongPressDown], a similar callback but for a secondary button. /// * [onTertiaryLongPressDown], a similar callback but for a tertiary button. /// * [LongPressGestureRecognizer.onSecondaryLongPressDown], which exposes /// this callback at the gesture layer. final GestureLongPressDownCallback? onSecondaryLongPressDown; /// A pointer that previously triggered [onSecondaryLongPressDown] will not /// end up causing a long-press. /// /// This triggers once the gesture loses if [onSecondaryLongPressDown] has /// previously been triggered. /// /// If the user completed the long-press, and the gesture won, then /// [onSecondaryLongPressStart] and [onSecondaryLongPress] are called instead. /// /// See also: /// /// * [kSecondaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onSecondaryLongPressCancel], which exposes /// this callback at the gesture layer. final GestureLongPressCancelCallback? onSecondaryLongPressCancel; /// Called when a long press gesture with a secondary button has been /// recognized. /// /// Triggered when a pointer has remained in contact with the screen at the /// same location for a long period of time. /// /// This is equivalent to (and is called immediately after) /// [onSecondaryLongPressStart]. The only difference between the two is that /// this callback does not contain details of the position at which the /// pointer initially contacted the screen. /// /// See also: /// /// * [kSecondaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onSecondaryLongPress], which exposes /// this callback at the gesture layer. final GestureLongPressCallback? onSecondaryLongPress; /// Called when a long press gesture with a secondary button has been /// recognized. /// /// Triggered when a pointer has remained in contact with the screen at the /// same location for a long period of time. /// /// This is equivalent to (and is called immediately before) /// [onSecondaryLongPress]. The only difference between the two is that this /// callback contains details of the position at which the pointer initially /// contacted the screen, whereas [onSecondaryLongPress] does not. /// /// See also: /// /// * [kSecondaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onSecondaryLongPressStart], which exposes /// this callback at the gesture layer. final GestureLongPressStartCallback? onSecondaryLongPressStart; /// A pointer has been drag-moved after a long press with a secondary button. /// /// See also: /// /// * [kSecondaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onSecondaryLongPressMoveUpdate], which exposes /// this callback at the gesture layer. final GestureLongPressMoveUpdateCallback? onSecondaryLongPressMoveUpdate; /// A pointer that has triggered a long-press with a secondary button has /// stopped contacting the screen. /// /// This is equivalent to (and is called immediately after) /// [onSecondaryLongPressEnd]. The only difference between the two is that /// this callback does not contain details of the state of the pointer when /// it stopped contacting the screen. /// /// See also: /// /// * [kSecondaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onSecondaryLongPressUp], which exposes /// this callback at the gesture layer. final GestureLongPressUpCallback? onSecondaryLongPressUp; /// A pointer that has triggered a long-press with a secondary button has /// stopped contacting the screen. /// /// This is equivalent to (and is called immediately before) /// [onSecondaryLongPressUp]. The only difference between the two is that /// this callback contains details of the state of the pointer when it /// stopped contacting the screen, whereas [onSecondaryLongPressUp] does not. /// /// See also: /// /// * [kSecondaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onSecondaryLongPressEnd], which exposes /// this callback at the gesture layer. final GestureLongPressEndCallback? onSecondaryLongPressEnd; /// The pointer has contacted the screen with a tertiary button, which might /// be the start of a long-press. /// /// This triggers after the pointer down event. /// /// If the user completes the long-press, and this gesture wins, /// [onTertiaryLongPressStart] will be called after this callback. Otherwise, /// [onTertiaryLongPressCancel] will be called after this callback. /// /// See also: /// /// * [kTertiaryButton], the button this callback responds to. /// * [onLongPressDown], a similar callback but for a primary button. /// * [onSecondaryLongPressDown], a similar callback but for a secondary button. /// * [LongPressGestureRecognizer.onTertiaryLongPressDown], which exposes /// this callback at the gesture layer. final GestureLongPressDownCallback? onTertiaryLongPressDown; /// A pointer that previously triggered [onTertiaryLongPressDown] will not /// end up causing a long-press. /// /// This triggers once the gesture loses if [onTertiaryLongPressDown] has /// previously been triggered. /// /// If the user completed the long-press, and the gesture won, then /// [onTertiaryLongPressStart] and [onTertiaryLongPress] are called instead. /// /// See also: /// /// * [kTertiaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onTertiaryLongPressCancel], which exposes /// this callback at the gesture layer. final GestureLongPressCancelCallback? onTertiaryLongPressCancel; /// Called when a long press gesture with a tertiary button has been /// recognized. /// /// Triggered when a pointer has remained in contact with the screen at the /// same location for a long period of time. /// /// This is equivalent to (and is called immediately after) /// [onTertiaryLongPressStart]. The only difference between the two is that /// this callback does not contain details of the position at which the /// pointer initially contacted the screen. /// /// See also: /// /// * [kTertiaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onTertiaryLongPress], which exposes /// this callback at the gesture layer. final GestureLongPressCallback? onTertiaryLongPress; /// Called when a long press gesture with a tertiary button has been /// recognized. /// /// Triggered when a pointer has remained in contact with the screen at the /// same location for a long period of time. /// /// This is equivalent to (and is called immediately before) /// [onTertiaryLongPress]. The only difference between the two is that this /// callback contains details of the position at which the pointer initially /// contacted the screen, whereas [onTertiaryLongPress] does not. /// /// See also: /// /// * [kTertiaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onTertiaryLongPressStart], which exposes /// this callback at the gesture layer. final GestureLongPressStartCallback? onTertiaryLongPressStart; /// A pointer has been drag-moved after a long press with a tertiary button. /// /// See also: /// /// * [kTertiaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onTertiaryLongPressMoveUpdate], which exposes /// this callback at the gesture layer. final GestureLongPressMoveUpdateCallback? onTertiaryLongPressMoveUpdate; /// A pointer that has triggered a long-press with a tertiary button has /// stopped contacting the screen. /// /// This is equivalent to (and is called immediately after) /// [onTertiaryLongPressEnd]. The only difference between the two is that /// this callback does not contain details of the state of the pointer when /// it stopped contacting the screen. /// /// See also: /// /// * [kTertiaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onTertiaryLongPressUp], which exposes /// this callback at the gesture layer. final GestureLongPressUpCallback? onTertiaryLongPressUp; /// A pointer that has triggered a long-press with a tertiary button has /// stopped contacting the screen. /// /// This is equivalent to (and is called immediately before) /// [onTertiaryLongPressUp]. The only difference between the two is that /// this callback contains details of the state of the pointer when it /// stopped contacting the screen, whereas [onTertiaryLongPressUp] does not. /// /// See also: /// /// * [kTertiaryButton], the button this callback responds to. /// * [LongPressGestureRecognizer.onTertiaryLongPressEnd], which exposes /// this callback at the gesture layer. final GestureLongPressEndCallback? onTertiaryLongPressEnd; /// A pointer has contacted the screen with a primary button and might begin /// to move vertically. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragDownCallback? onVerticalDragDown; /// A pointer has contacted the screen with a primary button and has begun to /// move vertically. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragStartCallback? onVerticalDragStart; /// A pointer that is in contact with the screen with a primary button and /// moving vertically has moved in the vertical direction. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragUpdateCallback? onVerticalDragUpdate; /// A pointer that was previously in contact with the screen with a primary /// button and moving vertically is no longer in contact with the screen and /// was moving at a specific velocity when it stopped contacting the screen. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragEndCallback? onVerticalDragEnd; /// The pointer that previously triggered [onVerticalDragDown] did not /// complete. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragCancelCallback? onVerticalDragCancel; /// A pointer has contacted the screen with a primary button and might begin /// to move horizontally. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragDownCallback? onHorizontalDragDown; /// A pointer has contacted the screen with a primary button and has begun to /// move horizontally. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragStartCallback? onHorizontalDragStart; /// A pointer that is in contact with the screen with a primary button and /// moving horizontally has moved in the horizontal direction. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragUpdateCallback? onHorizontalDragUpdate; /// A pointer that was previously in contact with the screen with a primary /// button and moving horizontally is no longer in contact with the screen and /// was moving at a specific velocity when it stopped contacting the screen. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragEndCallback? onHorizontalDragEnd; /// The pointer that previously triggered [onHorizontalDragDown] did not /// complete. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragCancelCallback? onHorizontalDragCancel; /// A pointer has contacted the screen with a primary button and might begin /// to move. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragDownCallback? onPanDown; /// A pointer has contacted the screen with a primary button and has begun to /// move. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragStartCallback? onPanStart; /// A pointer that is in contact with the screen with a primary button and /// moving has moved again. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragUpdateCallback? onPanUpdate; /// A pointer that was previously in contact with the screen with a primary /// button and moving is no longer in contact with the screen and was moving /// at a specific velocity when it stopped contacting the screen. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragEndCallback? onPanEnd; /// The pointer that previously triggered [onPanDown] did not complete. /// /// See also: /// /// * [kPrimaryButton], the button this callback responds to. final GestureDragCancelCallback? onPanCancel; /// The pointers in contact with the screen have established a focal point and /// initial scale of 1.0. final GestureScaleStartCallback? onScaleStart; /// The pointers in contact with the screen have indicated a new focal point /// and/or scale. final GestureScaleUpdateCallback? onScaleUpdate; /// The pointers are no longer in contact with the screen. final GestureScaleEndCallback? onScaleEnd; /// The pointer is in contact with the screen and has pressed with sufficient /// force to initiate a force press. The amount of force is at least /// [ForcePressGestureRecognizer.startPressure]. /// /// This callback will only be fired on devices with pressure /// detecting screens. final GestureForcePressStartCallback? onForcePressStart; /// The pointer is in contact with the screen and has pressed with the maximum /// force. The amount of force is at least /// [ForcePressGestureRecognizer.peakPressure]. /// /// This callback will only be fired on devices with pressure /// detecting screens. final GestureForcePressPeakCallback? onForcePressPeak; /// A pointer is in contact with the screen, has previously passed the /// [ForcePressGestureRecognizer.startPressure] and is either moving on the /// plane of the screen, pressing the screen with varying forces or both /// simultaneously. /// /// This callback will only be fired on devices with pressure /// detecting screens. final GestureForcePressUpdateCallback? onForcePressUpdate; /// The pointer tracked by [onForcePressStart] is no longer in contact with the screen. /// /// This callback will only be fired on devices with pressure /// detecting screens. final GestureForcePressEndCallback? onForcePressEnd; /// How this gesture detector should behave during hit testing when deciding /// how the hit test propagates to children and whether to consider targets /// behind this one. /// /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and /// [HitTestBehavior.translucent] if child is null. /// /// See [HitTestBehavior] for the allowed values and their meanings. final HitTestBehavior? behavior; /// Whether to exclude these gestures from the semantics tree. For /// example, the long-press gesture for showing a tooltip is /// excluded because the tooltip itself is included in the semantics /// tree directly and so having a gesture to show it would result in /// duplication of information. final bool excludeFromSemantics; /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], gesture drag behavior will /// begin at the position where the drag gesture won the arena. If set to /// [DragStartBehavior.down] it will begin at the position where a down event /// is first detected. /// /// In general, setting this to [DragStartBehavior.start] will make drag /// animation smoother and setting it to [DragStartBehavior.down] will make /// drag behavior feel slightly more reactive. /// /// By default, the drag start behavior is [DragStartBehavior.start]. /// /// Only the [DragGestureRecognizer.onStart] callbacks for the /// [VerticalDragGestureRecognizer], [HorizontalDragGestureRecognizer] and /// [PanGestureRecognizer] are affected by this setting. /// /// See also: /// /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. final DragStartBehavior dragStartBehavior; /// The kind of devices that are allowed to be recognized. /// /// If set to null, events from all device types will be recognized. Defaults to null. final Set? supportedDevices; /// {@macro flutter.gestures.scale.trackpadScrollCausesScale} final bool trackpadScrollCausesScale; /// {@macro flutter.gestures.scale.trackpadScrollToScaleFactor} final Offset trackpadScrollToScaleFactor; final Duration longPressTimeout; @override Widget build(BuildContext context) { final Map gestures = {}; final DeviceGestureSettings? gestureSettings = MediaQuery.maybeGestureSettingsOf(context); final ScrollBehavior configuration = ScrollConfiguration.of(context); if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null || onSecondaryTap != null || onSecondaryTapDown != null || onSecondaryTapUp != null || onSecondaryTapCancel != null || onTertiaryTapDown != null || onTertiaryTapUp != null || onTertiaryTapCancel != null) { gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers( () => TapGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), (instance) { instance ..onTapDown = onTapDown ..onTapUp = onTapUp ..onTap = onTap ..onTapCancel = onTapCancel ..onSecondaryTap = onSecondaryTap ..onSecondaryTapDown = onSecondaryTapDown ..onSecondaryTapUp = onSecondaryTapUp ..onSecondaryTapCancel = onSecondaryTapCancel ..onTertiaryTapDown = onTertiaryTapDown ..onTertiaryTapUp = onTertiaryTapUp ..onTertiaryTapCancel = onTertiaryTapCancel ..gestureSettings = gestureSettings ..supportedDevices = supportedDevices; }, ); } if (onDoubleTap != null || onDoubleTapDown != null || onDoubleTapCancel != null) { gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers( () => DoubleTapGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), (instance) { instance ..onDoubleTapDown = onDoubleTapDown ..onDoubleTap = onDoubleTap ..onDoubleTapCancel = onDoubleTapCancel ..gestureSettings = gestureSettings ..supportedDevices = supportedDevices; }, ); } if (onLongPressDown != null || onLongPressCancel != null || onLongPress != null || onLongPressStart != null || onLongPressMoveUpdate != null || onLongPressUp != null || onLongPressEnd != null || onSecondaryLongPressDown != null || onSecondaryLongPressCancel != null || onSecondaryLongPress != null || onSecondaryLongPressStart != null || onSecondaryLongPressMoveUpdate != null || onSecondaryLongPressUp != null || onSecondaryLongPressEnd != null || onTertiaryLongPressDown != null || onTertiaryLongPressCancel != null || onTertiaryLongPress != null || onTertiaryLongPressStart != null || onTertiaryLongPressMoveUpdate != null || onTertiaryLongPressUp != null || onTertiaryLongPressEnd != null) { gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers( () => LongPressGestureRecognizer(duration: longPressTimeout, debugOwner: this, supportedDevices: supportedDevices), (instance) { instance ..onLongPressDown = onLongPressDown ..onLongPressCancel = onLongPressCancel ..onLongPress = onLongPress ..onLongPressStart = onLongPressStart ..onLongPressMoveUpdate = onLongPressMoveUpdate ..onLongPressUp = onLongPressUp ..onLongPressEnd = onLongPressEnd ..onSecondaryLongPressDown = onSecondaryLongPressDown ..onSecondaryLongPressCancel = onSecondaryLongPressCancel ..onSecondaryLongPress = onSecondaryLongPress ..onSecondaryLongPressStart = onSecondaryLongPressStart ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate ..onSecondaryLongPressUp = onSecondaryLongPressUp ..onSecondaryLongPressEnd = onSecondaryLongPressEnd ..onTertiaryLongPressDown = onTertiaryLongPressDown ..onTertiaryLongPressCancel = onTertiaryLongPressCancel ..onTertiaryLongPress = onTertiaryLongPress ..onTertiaryLongPressStart = onTertiaryLongPressStart ..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate ..onTertiaryLongPressUp = onTertiaryLongPressUp ..onTertiaryLongPressEnd = onTertiaryLongPressEnd ..gestureSettings = gestureSettings ..supportedDevices = supportedDevices; }, ); } if (onVerticalDragDown != null || onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null || onVerticalDragCancel != null) { gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers( () => VerticalDragGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), (instance) { instance ..onDown = onVerticalDragDown ..onStart = onVerticalDragStart ..onUpdate = onVerticalDragUpdate ..onEnd = onVerticalDragEnd ..onCancel = onVerticalDragCancel ..dragStartBehavior = dragStartBehavior ..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context) ..gestureSettings = gestureSettings ..supportedDevices = supportedDevices; }, ); } if (onHorizontalDragDown != null || onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null || onHorizontalDragCancel != null) { gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers( () => HorizontalDragGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), (instance) { instance ..onDown = onHorizontalDragDown ..onStart = onHorizontalDragStart ..onUpdate = onHorizontalDragUpdate ..onEnd = onHorizontalDragEnd ..onCancel = onHorizontalDragCancel ..dragStartBehavior = dragStartBehavior ..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context) ..gestureSettings = gestureSettings ..supportedDevices = supportedDevices; }, ); } if (onPanDown != null || onPanStart != null || onPanUpdate != null || onPanEnd != null || onPanCancel != null) { gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers( () => PanGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), (instance) { instance ..onDown = onPanDown ..onStart = onPanStart ..onUpdate = onPanUpdate ..onEnd = onPanEnd ..onCancel = onPanCancel ..dragStartBehavior = dragStartBehavior ..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context) ..gestureSettings = gestureSettings ..supportedDevices = supportedDevices; }, ); } if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) { gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers( () => ScaleGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), (instance) { instance ..onStart = onScaleStart ..onUpdate = onScaleUpdate ..onEnd = onScaleEnd ..dragStartBehavior = dragStartBehavior ..gestureSettings = gestureSettings ..trackpadScrollCausesScale = trackpadScrollCausesScale ..trackpadScrollToScaleFactor = trackpadScrollToScaleFactor ..supportedDevices = supportedDevices; }, ); } if (onForcePressStart != null || onForcePressPeak != null || onForcePressUpdate != null || onForcePressEnd != null) { gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers( () => ForcePressGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), (instance) { instance ..onStart = onForcePressStart ..onPeak = onForcePressPeak ..onUpdate = onForcePressUpdate ..onEnd = onForcePressEnd ..gestureSettings = gestureSettings ..supportedDevices = supportedDevices; }, ); } return RawGestureDetector( gestures: gestures, behavior: behavior, excludeFromSemantics: excludeFromSemantics, child: child, ); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(EnumProperty('startBehavior', dragStartBehavior)); } }