diff --git a/packages/flutter/lib/src/cupertino/switch.dart b/packages/flutter/lib/src/cupertino/switch.dart index 8c0a7bf265c..8cf8ea1a0c1 100644 --- a/packages/flutter/lib/src/cupertino/switch.dart +++ b/packages/flutter/lib/src/cupertino/switch.dart @@ -110,9 +110,9 @@ class CupertinoSwitch extends StatefulWidget { /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], the drag behavior used to move the - /// switch from on to off will begin upon the detection of a drag gesture. If - /// set to [DragStartBehavior.down] it will begin when a down event is first - /// detected. + /// switch from on to off 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 was first detected. /// /// In general, setting this to [DragStartBehavior.start] will make drag /// animation smoother and setting it to [DragStartBehavior.down] will make diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart index e48caceb690..6e14b94041f 100644 --- a/packages/flutter/lib/src/gestures/monodrag.dart +++ b/packages/flutter/lib/src/gestures/monodrag.dart @@ -82,12 +82,15 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ); static VelocityTracker _defaultBuilder(PointerEvent event) => VelocityTracker.withKind(event.kind); - /// Configure the behavior of offsets sent to [onStart]. + + /// Configure the behavior of offsets passed to [onStart]. /// /// If set to [DragStartBehavior.start], the [onStart] callback will be called - /// at the time and position when this gesture recognizer wins the arena. If - /// [DragStartBehavior.down], [onStart] will be called at the time and - /// position when a down event was first detected. + /// with the position of the pointer at the time this gesture recognizer won + /// the arena. If [DragStartBehavior.down], [onStart] will be called with + /// the position of the first detected down event for the pointer. When there + /// are no other gestures competing with this gesture in the arena, there's + /// no difference in behavior between the two settings. /// /// For more information about the gesture arena: /// https://flutter.dev/docs/development/ui/advanced/gestures#gesture-disambiguation @@ -96,13 +99,14 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { /// /// ## Example: /// - /// A finger presses down on the screen with offset (500.0, 500.0), and then - /// moves to position (510.0, 500.0) before winning the arena. With + /// A [HorizontalDragGestureRecognizer] and a [VerticalDragGestureRecognizer] + /// compete with each other. A finger presses down on the screen with + /// offset (500.0, 500.0), and then moves to position (510.0, 500.0) before + /// the [HorizontalDragGestureRecognizer] wins the arena. With /// [dragStartBehavior] set to [DragStartBehavior.down], the [onStart] - /// callback will be called at the time corresponding to the touch's position - /// at (500.0, 500.0). If it is instead set to [DragStartBehavior.start], - /// [onStart] will be called at the time corresponding to the touch's position - /// at (510.0, 500.0). + /// callback will be called with position (500.0, 500.0). If it is + /// instead set to [DragStartBehavior.start], [onStart] will be called with + /// position (510.0, 500.0). DragStartBehavior dragStartBehavior; /// A pointer has contacted the screen with a primary button and might begin @@ -121,12 +125,8 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { /// move. /// /// The position of the pointer is provided in the callback's `details` - /// argument, which is a [DragStartDetails] object. - /// - /// Depending on the value of [dragStartBehavior], this function will be - /// called on the initial touch down, if set to [DragStartBehavior.down] or - /// when the drag gesture is first detected, if set to - /// [DragStartBehavior.start]. + /// argument, which is a [DragStartDetails] object. The [dragStartBehavior] + /// determines this position. /// /// See also: /// diff --git a/packages/flutter/lib/src/gestures/recognizer.dart b/packages/flutter/lib/src/gestures/recognizer.dart index f904b5d9ae3..f353c923ab6 100644 --- a/packages/flutter/lib/src/gestures/recognizer.dart +++ b/packages/flutter/lib/src/gestures/recognizer.dart @@ -27,19 +27,17 @@ typedef RecognizerCallback = T Function(); /// Configuration of offset passed to [DragStartDetails]. /// -/// The settings determines when a drag formally starts when the user -/// initiates a drag. -/// /// See also: /// -/// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. +/// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the +/// different behaviors. enum DragStartBehavior { - /// Set the initial offset, at the position where the first down event was + /// Set the initial offset at the position where the first down event was /// detected. down, - /// Set the initial position at the position where the drag start event was - /// detected. + /// Set the initial position at the position where this gesture recognizer + /// won the arena. start, } diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index 661677b2e23..b20b9e52e3b 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -2273,9 +2273,9 @@ class _MonthItem extends StatefulWidget { /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], the drag gesture used to scroll a - /// date picker wheel will begin upon the detection of a drag gesture. If set - /// to [DragStartBehavior.down] it will begin when a down event is first - /// detected. + /// date picker wheel 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 diff --git a/packages/flutter/lib/src/material/date_picker_deprecated.dart b/packages/flutter/lib/src/material/date_picker_deprecated.dart index a9a2087f58e..488c4d3c3f9 100644 --- a/packages/flutter/lib/src/material/date_picker_deprecated.dart +++ b/packages/flutter/lib/src/material/date_picker_deprecated.dart @@ -124,9 +124,9 @@ class DayPicker extends StatelessWidget { /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], the drag gesture used to scroll a - /// date picker wheel will begin upon the detection of a drag gesture. If set - /// to [DragStartBehavior.down] it will begin when a down event is first - /// detected. + /// date picker wheel 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 diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index d896db58f07..ca99185d61d 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -259,9 +259,9 @@ class DrawerController extends StatefulWidget { /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], the drag behavior used for opening - /// and closing a drawer will begin upon the detection of a drag gesture. If - /// set to [DragStartBehavior.down] it will begin when a down event is first - /// detected. + /// and closing a drawer 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 diff --git a/packages/flutter/lib/src/widgets/dismissible.dart b/packages/flutter/lib/src/widgets/dismissible.dart index 5a43f391581..9d7108fa7c7 100644 --- a/packages/flutter/lib/src/widgets/dismissible.dart +++ b/packages/flutter/lib/src/widgets/dismissible.dart @@ -212,8 +212,9 @@ class Dismissible extends StatefulWidget { /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], the drag gesture used to dismiss a - /// dismissible will begin upon the detection of a drag gesture. If set to - /// [DragStartBehavior.down] it will begin when a down event is first detected. + /// dismissible 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 diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index c9d11856fa0..f8d8173810f 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -975,8 +975,9 @@ class GestureDetector extends StatelessWidget { /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], gesture drag behavior will - /// begin upon the detection of a drag gesture. If set to - /// [DragStartBehavior.down] it will begin when a down event is first detected. + /// 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 diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 4198cfe7ec6..27792796f92 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -214,8 +214,9 @@ class Scrollable extends StatefulWidget { /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], scrolling drag behavior will - /// begin upon the detection of a drag gesture. If set to - /// [DragStartBehavior.down] it will begin when a down event is first detected. + /// 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 diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 45aaa3ac28d..4c4f2591a26 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -385,8 +385,9 @@ class TextSelectionOverlay { /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], handle drag behavior will - /// begin upon the detection of a drag gesture. If set to - /// [DragStartBehavior.down] it will begin when a down event is first detected. + /// 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 diff --git a/packages/flutter/test/gestures/drag_test.dart b/packages/flutter/test/gestures/drag_test.dart index 67afcfa5f6c..b0ff102c5c4 100644 --- a/packages/flutter/test/gestures/drag_test.dart +++ b/packages/flutter/test/gestures/drag_test.dart @@ -281,6 +281,125 @@ void main() { expect(didEndDrag, isFalse); }); + testGesture('DragGestureRecognizer.onStart behavior test', (GestureTester tester) { + final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer() + ..dragStartBehavior = DragStartBehavior.down; + addTearDown(drag.dispose); + + Duration? startTimestamp; + Offset? positionAtOnStart; + drag.onStart = (DragStartDetails details) { + startTimestamp = details.sourceTimeStamp; + positionAtOnStart = details.globalPosition; + }; + + Duration? updatedTimestamp; + Offset? updateDelta; + drag.onUpdate = (DragUpdateDetails details) { + updatedTimestamp = details.sourceTimeStamp; + updateDelta = details.delta; + }; + + // No competing, dragStartBehavior == DragStartBehavior.down + final TestPointer pointer = TestPointer(5); + PointerDownEvent down = pointer.down(const Offset(10.0, 10.0), timeStamp: const Duration(milliseconds: 100)); + drag.addPointer(down); + tester.closeArena(5); + expect(startTimestamp, isNull); + expect(positionAtOnStart, isNull); + expect(updatedTimestamp, isNull); + + tester.route(down); + // The only horizontal drag gesture win the arena when the pointer down. + expect(startTimestamp, const Duration(milliseconds: 100)); + expect(positionAtOnStart, const Offset(10.0, 10.0)); + expect(updatedTimestamp, isNull); + + tester.route(pointer.move(const Offset(20.0, 25.0), timeStamp: const Duration(milliseconds: 200))); + expect(updatedTimestamp, const Duration(milliseconds: 200)); + expect(updateDelta, const Offset(10.0, 0.0)); + + tester.route(pointer.move(const Offset(20.0, 25.0), timeStamp: const Duration(milliseconds: 300))); + expect(updatedTimestamp, const Duration(milliseconds: 300)); + expect(updateDelta, Offset.zero); + tester.route(pointer.up()); + + // No competing, dragStartBehavior == DragStartBehavior.start + // When there are no other gestures competing with this gesture in the arena, + // there's no difference in behavior between the two settings. + drag.dragStartBehavior = DragStartBehavior.start; + startTimestamp = null; + positionAtOnStart = null; + updatedTimestamp = null; + updateDelta = null; + + down = pointer.down(const Offset(10.0, 10.0), timeStamp: const Duration(milliseconds: 400)); + drag.addPointer(down); + tester.closeArena(5); + tester.route(down); + + expect(startTimestamp, const Duration(milliseconds: 400)); + expect(positionAtOnStart, const Offset(10.0, 10.0)); + expect(updatedTimestamp, isNull); + + tester.route(pointer.move(const Offset(20.0, 25.0), timeStamp: const Duration(milliseconds: 500))); + expect(updatedTimestamp, const Duration(milliseconds: 500)); + tester.route(pointer.up()); + + // With competing, dragStartBehavior == DragStartBehavior.start + startTimestamp = null; + positionAtOnStart = null; + updatedTimestamp = null; + updateDelta = null; + + final VerticalDragGestureRecognizer competingDrag = VerticalDragGestureRecognizer() + ..onStart = (_) {}; + addTearDown(() => competingDrag.dispose); + + down = pointer.down(const Offset(10.0, 10.0), timeStamp: const Duration(milliseconds: 600)); + drag.addPointer(down); + competingDrag.addPointer(down); + tester.closeArena(5); + tester.route(down); + + // The pointer down event do not trigger anything. + expect(startTimestamp, isNull); + expect(positionAtOnStart, isNull); + expect(updatedTimestamp, isNull); + + tester.route(pointer.move(const Offset(30.0, 10.0), timeStamp: const Duration(milliseconds: 700))); + expect(startTimestamp, const Duration(milliseconds: 700)); + // Using the position of the pointer at the time this gesture recognizer won the arena. + expect(positionAtOnStart, const Offset(30.0, 10.0)); + expect(updatedTimestamp, isNull); // Do not trigger an update event. + tester.route(pointer.up()); + + // With competing, dragStartBehavior == DragStartBehavior.down + drag.dragStartBehavior = DragStartBehavior.down; + startTimestamp = null; + positionAtOnStart = null; + updatedTimestamp = null; + updateDelta = null; + + down = pointer.down(const Offset(10.0, 10.0), timeStamp: const Duration(milliseconds: 800)); + drag.addPointer(down); + competingDrag.addPointer(down); + tester.closeArena(5); + tester.route(down); + + expect(startTimestamp, isNull); + expect(positionAtOnStart, isNull); + expect(updatedTimestamp, isNull); + + tester.route(pointer.move(const Offset(30.0, 10.0), timeStamp: const Duration(milliseconds: 900))); + expect(startTimestamp, const Duration(milliseconds: 900)); + // Using the position of the first detected down event for the pointer. + expect(positionAtOnStart, const Offset(10.0, 10.0)); + expect(updatedTimestamp, const Duration(milliseconds: 900)); // Also, trigger an update event. + expect(updateDelta, const Offset(20.0, 0.0)); + tester.route(pointer.up()); + }); + testGesture('Should report original timestamps', (GestureTester tester) { final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; addTearDown(drag.dispose);