From 47724f97fa1abddf09d29e060bf0fd9f2f83f125 Mon Sep 17 00:00:00 2001 From: jslavitz Date: Mon, 25 Feb 2019 14:15:31 -0800 Subject: [PATCH] Breaks the moveBy call from drag and dragFrom into two separate calls and changes the default behavior of DragStartBehavior to DragStartBehavior.start (#26438) * Breaking change which sets the default DragStartBehavior to DragStartBehavior.start and changes WidgetTester.drag and WidgetTester.dragFrom by breaking the moveBy function call into multiple pieces. --- .../flutter/lib/src/cupertino/switch.dart | 9 +- .../flutter/lib/src/gestures/constants.dart | 4 + .../flutter/lib/src/gestures/monodrag.dart | 2 +- .../flutter/lib/src/material/date_picker.dart | 9 +- packages/flutter/lib/src/material/drawer.dart | 5 +- .../src/material/paginated_data_table.dart | 2 +- .../flutter/lib/src/material/scaffold.dart | 2 +- packages/flutter/lib/src/material/switch.dart | 4 +- packages/flutter/lib/src/material/tabs.dart | 4 +- .../flutter/lib/src/material/text_field.dart | 2 +- .../flutter/lib/src/widgets/dismissible.dart | 5 +- .../lib/src/widgets/editable_text.dart | 2 +- .../lib/src/widgets/gesture_detector.dart | 5 +- .../lib/src/widgets/nested_scroll_view.dart | 4 +- .../flutter/lib/src/widgets/page_view.dart | 6 +- .../lib/src/widgets/scroll_activity.dart | 1 - .../flutter/lib/src/widgets/scroll_view.dart | 16 +- .../flutter/lib/src/widgets/scrollable.dart | 4 +- .../src/widgets/single_child_scroll_view.dart | 2 +- .../lib/src/widgets/text_selection.dart | 7 +- .../test/cupertino/date_picker_test.dart | 20 +- .../flutter/test/cupertino/picker_test.dart | 4 +- .../flutter/test/cupertino/refresh_test.dart | 24 +- .../flutter/test/cupertino/switch_test.dart | 9 +- packages/flutter/test/gestures/drag_test.dart | 55 +++- .../test/widgets/gesture_detector_test.dart | 2 +- .../widgets/list_view_horizontal_test.dart | 48 +-- .../test/widgets/scroll_controller_test.dart | 2 +- .../flutter/test/widgets/scrollable_test.dart | 2 +- packages/flutter_test/lib/src/controller.dart | 100 ++++++- .../flutter_test/test/controller_test.dart | 282 ++++++++++++++++++ 31 files changed, 532 insertions(+), 111 deletions(-) create mode 100644 packages/flutter_test/test/controller_test.dart diff --git a/packages/flutter/lib/src/cupertino/switch.dart b/packages/flutter/lib/src/cupertino/switch.dart index 1dee9efd490..56414a443ab 100644 --- a/packages/flutter/lib/src/cupertino/switch.dart +++ b/packages/flutter/lib/src/cupertino/switch.dart @@ -60,7 +60,7 @@ class CupertinoSwitch extends StatefulWidget { @required this.value, @required this.onChanged, this.activeColor, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(dragStartBehavior != null), super(key: key); @@ -97,7 +97,6 @@ class CupertinoSwitch extends StatefulWidget { /// [CupertinoTheme] in accordance to native iOS behavior. final Color activeColor; - // TODO(jslavitz): Set the DragStartBehavior default to be start across all widgets. /// {@template flutter.cupertino.switch.dragStartBehavior} /// Determines the way that drag start behavior is handled. /// @@ -110,7 +109,7 @@ class CupertinoSwitch extends StatefulWidget { /// animation smoother and setting it to [DragStartBehavior.down] will make /// drag behavior feel slightly more reactive. /// - /// By default, the drag start behavior is [DragStartBehavior.down]. + /// By default, the drag start behavior is [DragStartBehavior.start]. /// /// See also: /// @@ -149,7 +148,7 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { this.activeColor, this.onChanged, this.vsync, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : super(key: key); final bool value; @@ -202,7 +201,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { ValueChanged onChanged, @required TextDirection textDirection, @required TickerProvider vsync, - DragStartBehavior dragStartBehavior = DragStartBehavior.down, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : assert(value != null), assert(activeColor != null), assert(vsync != null), diff --git a/packages/flutter/lib/src/gestures/constants.dart b/packages/flutter/lib/src/gestures/constants.dart index bd33d9ced5d..2979dd28a02 100644 --- a/packages/flutter/lib/src/gestures/constants.dart +++ b/packages/flutter/lib/src/gestures/constants.dart @@ -54,6 +54,10 @@ const Duration kZoomControlsTimeout = Duration(milliseconds: 3000); /// the gesture is a scroll gesture, or, inversely, the maximum distance that a /// touch can travel before the framework becomes confident that it is not a /// tap. +/// +/// A total delta less than or equal to [kTouchSlop] is not considered to be a +/// drag, whereas if the delta is greater than [kTouchSlop] it is considered to +/// be a drag. // This value was empirically derived. We started at 8.0 and increased it to // 18.0 after getting complaints that it was too difficult to hit targets. const double kTouchSlop = 18.0; // Logical pixels diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart index 494655d6fe1..64223be4dd6 100644 --- a/packages/flutter/lib/src/gestures/monodrag.dart +++ b/packages/flutter/lib/src/gestures/monodrag.dart @@ -54,7 +54,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { /// [dragStartBehavior] must not be null. DragGestureRecognizer({ Object debugOwner, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(dragStartBehavior != null), super(debugOwner: debugOwner); diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index d37e86d8a7d..8ba509d5ebb 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -254,7 +254,7 @@ class DayPicker extends StatelessWidget { @required this.lastDate, @required this.displayedMonth, this.selectableDayPredicate, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(selectedDate != null), assert(currentDate != null), assert(onChanged != null), @@ -287,7 +287,6 @@ class DayPicker extends StatelessWidget { /// Optional user supplied predicate function to customize selectable days. final SelectableDayPredicate selectableDayPredicate; - // TODO(jslavitz): Set the DragStartBehavior default to be start across all widgets. /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], the drag gesture used to scroll a @@ -299,7 +298,7 @@ class DayPicker extends StatelessWidget { /// animation smoother and setting it to [DragStartBehavior.down] will make /// drag behavior feel slightly more reactive. /// - /// By default, the drag start behavior is [DragStartBehavior.down]. + /// By default, the drag start behavior is [DragStartBehavior.start]. /// /// See also: /// @@ -528,7 +527,7 @@ class MonthPicker extends StatefulWidget { @required this.firstDate, @required this.lastDate, this.selectableDayPredicate, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(selectedDate != null), assert(onChanged != null), assert(!firstDate.isAfter(lastDate)), @@ -791,7 +790,7 @@ class YearPicker extends StatefulWidget { @required this.onChanged, @required this.firstDate, @required this.lastDate, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(selectedDate != null), assert(onChanged != null), assert(!firstDate.isAfter(lastDate)), diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index 885d20827b8..0de19870043 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -180,7 +180,7 @@ class DrawerController extends StatefulWidget { @required this.child, @required this.alignment, this.drawerCallback, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(child != null), assert(dragStartBehavior != null), assert(alignment != null), @@ -200,7 +200,6 @@ class DrawerController extends StatefulWidget { /// Optional callback that is called when a [Drawer] is opened or closed. final DrawerCallback drawerCallback; - // TODO(jslavitz): Set the DragStartBehavior default to be start across all widgets. /// {@template flutter.material.drawer.dragStartBehavior} /// Determines the way that drag start behavior is handled. /// @@ -213,7 +212,7 @@ class DrawerController extends StatefulWidget { /// animation smoother and setting it to [DragStartBehavior.down] will make /// drag behavior feel slightly more reactive. /// - /// By default, the drag start behavior is [DragStartBehavior.down]. + /// By default, the drag start behavior is [DragStartBehavior.start]. /// /// See also: /// diff --git a/packages/flutter/lib/src/material/paginated_data_table.dart b/packages/flutter/lib/src/material/paginated_data_table.dart index dcca71387dd..162e8db5019 100644 --- a/packages/flutter/lib/src/material/paginated_data_table.dart +++ b/packages/flutter/lib/src/material/paginated_data_table.dart @@ -75,7 +75,7 @@ class PaginatedDataTable extends StatefulWidget { this.rowsPerPage = defaultRowsPerPage, this.availableRowsPerPage = const [defaultRowsPerPage, defaultRowsPerPage * 2, defaultRowsPerPage * 5, defaultRowsPerPage * 10], this.onRowsPerPageChanged, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, @required this.source }) : assert(header != null), assert(columns != null), diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index f9b8d598e2f..133ba76b42d 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -876,8 +876,8 @@ class Scaffold extends StatefulWidget { this.resizeToAvoidBottomPadding, this.resizeToAvoidBottomInset, this.primary = true, + this.drawerDragStartBehavior = DragStartBehavior.start, this.extendBody = false, - this.drawerDragStartBehavior = DragStartBehavior.down, }) : assert(primary != null), assert(extendBody != null), assert(drawerDragStartBehavior != null), diff --git a/packages/flutter/lib/src/material/switch.dart b/packages/flutter/lib/src/material/switch.dart index 5a55e06aefd..65fc7987cda 100644 --- a/packages/flutter/lib/src/material/switch.dart +++ b/packages/flutter/lib/src/material/switch.dart @@ -73,7 +73,7 @@ class Switch extends StatefulWidget { this.activeThumbImage, this.inactiveThumbImage, this.materialTapTargetSize, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : _switchType = _SwitchType.material, assert(dragStartBehavior != null), super(key: key); @@ -97,7 +97,7 @@ class Switch extends StatefulWidget { this.activeThumbImage, this.inactiveThumbImage, this.materialTapTargetSize, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : _switchType = _SwitchType.adaptive, super(key: key); diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index affb0a045d6..5a6ac3f7a90 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -562,7 +562,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { this.labelPadding, this.unselectedLabelColor, this.unselectedLabelStyle, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, this.onTap, }) : assert(tabs != null), assert(isScrollable != null), @@ -1057,7 +1057,7 @@ class TabBarView extends StatefulWidget { @required this.children, this.controller, this.physics, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(children != null), assert(dragStartBehavior != null), super(key: key); diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index c0795748760..6c99f437d4c 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -143,7 +143,7 @@ class TextField extends StatefulWidget { this.cursorColor, this.keyboardAppearance, this.scrollPadding = const EdgeInsets.all(20.0), - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, this.enableInteractiveSelection, this.onTap, this.buildCounter, diff --git a/packages/flutter/lib/src/widgets/dismissible.dart b/packages/flutter/lib/src/widgets/dismissible.dart index f1a3d95a08f..d57571173bc 100644 --- a/packages/flutter/lib/src/widgets/dismissible.dart +++ b/packages/flutter/lib/src/widgets/dismissible.dart @@ -91,7 +91,7 @@ class Dismissible extends StatefulWidget { this.dismissThresholds = const {}, this.movementDuration = const Duration(milliseconds: 200), this.crossAxisEndOffset = 0.0, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(key != null), assert(secondaryBackground != null ? background != null : true), assert(dragStartBehavior != null), @@ -162,7 +162,6 @@ class Dismissible extends StatefulWidget { /// it is positive or negative. final double crossAxisEndOffset; - // TODO(jslavitz): Set the DragStartBehavior default to be start across all widgets. /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], the drag gesture used to dismiss a @@ -173,7 +172,7 @@ class Dismissible extends StatefulWidget { /// animation smoother and setting it to [DragStartBehavior.down] will make /// drag behavior feel slightly more reactive. /// - /// By default, the drag start behavior is [DragStartBehavior.down]. + /// By default, the drag start behavior is [DragStartBehavior.start]. /// /// See also: /// diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index e5a792e563a..0bdfe56e23e 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -231,7 +231,7 @@ class EditableText extends StatefulWidget { this.paintCursorAboveText = false, this.scrollPadding = const EdgeInsets.all(20.0), this.keyboardAppearance = Brightness.light, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, this.enableInteractiveSelection, }) : assert(controller != null), assert(focusNode != null), diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index 1c50ee1c4bd..bfba20ff8be 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -186,7 +186,7 @@ class GestureDetector extends StatelessWidget { this.onScaleEnd, this.behavior, this.excludeFromSemantics = false, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(excludeFromSemantics != null), assert(dragStartBehavior != null), assert(() { @@ -373,7 +373,6 @@ class GestureDetector extends StatelessWidget { /// duplication of information. final bool excludeFromSemantics; - // TODO(jslavitz): Set the DragStartBehavior default to be start across all widgets. /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], gesture drag behavior will @@ -384,7 +383,7 @@ class GestureDetector extends StatelessWidget { /// animation smoother and setting it to [DragStartBehavior.down] will make /// drag behavior feel slightly more reactive. /// - /// By default, the drag start behavior is [DragStartBehavior.down]. + /// By default, the drag start behavior is [DragStartBehavior.start]. /// /// Only the [onStart] callbacks for the [VerticalDragGestureRecognizer], /// [HorizontalDragGestureRecognizer] and [PanGestureRecognizer] are affected diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index 4dc7cdcd2b0..5a1de311397 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -189,7 +189,7 @@ class NestedScrollView extends StatefulWidget { this.physics, @required this.headerSliverBuilder, @required this.body, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(scrollDirection != null), assert(reverse != null), assert(headerSliverBuilder != null), @@ -371,7 +371,7 @@ class _NestedScrollViewCustomScrollView extends CustomScrollView { @required ScrollController controller, @required List slivers, @required this.handle, - DragStartBehavior dragStartBehavior = DragStartBehavior.down, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : super( scrollDirection: scrollDirection, reverse: reverse, diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart index 9f8256dbfc4..329a0cd045b 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -425,7 +425,7 @@ class PageView extends StatefulWidget { this.pageSnapping = true, this.onPageChanged, List children = const [], - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : controller = controller ?? _defaultPageController, childrenDelegate = SliverChildListDelegate(children), super(key: key); @@ -452,7 +452,7 @@ class PageView extends StatefulWidget { this.onPageChanged, @required IndexedWidgetBuilder itemBuilder, int itemCount, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : controller = controller ?? _defaultPageController, childrenDelegate = SliverChildBuilderDelegate(itemBuilder, childCount: itemCount), super(key: key); @@ -468,7 +468,7 @@ class PageView extends StatefulWidget { this.pageSnapping = true, this.onPageChanged, @required this.childrenDelegate, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(childrenDelegate != null), controller = controller ?? _defaultPageController, super(key: key); diff --git a/packages/flutter/lib/src/widgets/scroll_activity.dart b/packages/flutter/lib/src/widgets/scroll_activity.dart index c60a5e000ae..da63935a89e 100644 --- a/packages/flutter/lib/src/widgets/scroll_activity.dart +++ b/packages/flutter/lib/src/widgets/scroll_activity.dart @@ -312,7 +312,6 @@ class ScrollDragController implements Drag { // May be null for proxied drags like via accessibility. return offset; } - if (offset == 0.0) { if (motionStartDistanceThreshold != null && _offsetSinceLastStop == null && diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart index 80690f08ab5..74e5ffe3ed1 100644 --- a/packages/flutter/lib/src/widgets/scroll_view.dart +++ b/packages/flutter/lib/src/widgets/scroll_view.dart @@ -69,7 +69,7 @@ abstract class ScrollView extends StatelessWidget { this.anchor = 0.0, this.cacheExtent, this.semanticChildCount, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(scrollDirection != null), assert(reverse != null), assert(shrinkWrap != null), @@ -454,7 +454,7 @@ class CustomScrollView extends ScrollView { double cacheExtent, this.slivers = const [], int semanticChildCount, - DragStartBehavior dragStartBehavior = DragStartBehavior.down, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : super( key: key, scrollDirection: scrollDirection, @@ -500,7 +500,7 @@ abstract class BoxScrollView extends ScrollView { this.padding, double cacheExtent, int semanticChildCount, - DragStartBehavior dragStartBehavior = DragStartBehavior.down, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : super( key: key, scrollDirection: scrollDirection, @@ -802,7 +802,7 @@ class ListView extends BoxScrollView { double cacheExtent, List children = const [], int semanticChildCount, - DragStartBehavior dragStartBehavior = DragStartBehavior.down, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : childrenDelegate = SliverChildListDelegate( children, addAutomaticKeepAlives: addAutomaticKeepAlives, @@ -866,7 +866,7 @@ class ListView extends BoxScrollView { bool addSemanticIndexes = true, double cacheExtent, int semanticChildCount, - DragStartBehavior dragStartBehavior = DragStartBehavior.down, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : childrenDelegate = SliverChildBuilderDelegate( itemBuilder, childCount: itemCount, @@ -1320,7 +1320,7 @@ class GridView extends BoxScrollView { @required this.childrenDelegate, double cacheExtent, int semanticChildCount, - DragStartBehavior dragStartBehavior = DragStartBehavior.down, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : assert(gridDelegate != null), assert(childrenDelegate != null), super( @@ -1370,7 +1370,7 @@ class GridView extends BoxScrollView { double cacheExtent, List children = const [], int semanticChildCount, - DragStartBehavior dragStartBehavior = DragStartBehavior.down, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, mainAxisSpacing: mainAxisSpacing, @@ -1429,7 +1429,7 @@ class GridView extends BoxScrollView { bool addSemanticIndexes = true, List children = const [], int semanticChildCount, - DragStartBehavior dragStartBehavior = DragStartBehavior.down, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: maxCrossAxisExtent, mainAxisSpacing: mainAxisSpacing, diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index eeabba0c1a9..4dad42fe10b 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -81,7 +81,7 @@ class Scrollable extends StatefulWidget { @required this.viewportBuilder, this.excludeFromSemantics = false, this.semanticChildCount, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(axisDirection != null), assert(dragStartBehavior != null), assert(viewportBuilder != null), @@ -194,7 +194,7 @@ class Scrollable extends StatefulWidget { /// animation smoother and setting it to [DragStartBehavior.down] will make /// drag behavior feel slightly more reactive. /// - /// By default, the drag start behavior is [DragStartBehavior.down]. + /// By default, the drag start behavior is [DragStartBehavior.start]. /// /// See also: /// diff --git a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart index 65b59bda3e3..8b0780bce31 100644 --- a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart @@ -204,7 +204,7 @@ class SingleChildScrollView extends StatelessWidget { this.physics, this.controller, this.child, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(scrollDirection != null), assert(dragStartBehavior != null), assert(!(controller != null && primary == true), diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 40c39c7c9bf..fe4606b6516 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -230,7 +230,7 @@ class TextSelectionOverlay { @required this.renderObject, this.selectionControls, this.selectionDelegate, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(value != null), assert(context != null), _value = value { @@ -265,7 +265,6 @@ class TextSelectionOverlay { /// text field. final TextSelectionDelegate selectionDelegate; - // TODO(jslavitz): Set the DragStartBehavior default to be start across all widgets. /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], handle drag behavior will @@ -276,7 +275,7 @@ class TextSelectionOverlay { /// animation smoother and setting it to [DragStartBehavior.down] will make /// drag behavior feel slightly more reactive. /// - /// By default, the drag start behavior is [DragStartBehavior.down]. + /// By default, the drag start behavior is [DragStartBehavior.start]. /// /// See also: /// @@ -468,7 +467,7 @@ class _TextSelectionHandleOverlay extends StatefulWidget { @required this.onSelectionHandleChanged, @required this.onSelectionHandleTapped, @required this.selectionControls, - this.dragStartBehavior = DragStartBehavior.down, + this.dragStartBehavior = DragStartBehavior.start, }) : super(key: key); final TextSelection selection; diff --git a/packages/flutter/test/cupertino/date_picker_test.dart b/packages/flutter/test/cupertino/date_picker_test.dart index e43337c7a3e..70aececfb9e 100644 --- a/packages/flutter/test/cupertino/date_picker_test.dart +++ b/packages/flutter/test/cupertino/date_picker_test.dart @@ -8,7 +8,7 @@ import 'package:flutter/semantics.dart'; import 'package:flutter_test/flutter_test.dart'; // scrolling by this offset will move the picker to the next item -const Offset _kRowOffset = Offset(0.0, -32.0); +const Offset _kRowOffset = Offset(0.0, -50.0); void main() { group('Countdown timer picker', () { @@ -280,7 +280,7 @@ void main() { ), ); - await tester.drag(find.text('10'), const Offset(0.0, 32.0)); + await tester.drag(find.text('10'), const Offset(0.0, 32.0), touchSlopY: 0); await tester.pump(); await tester.pump(const Duration(milliseconds: 500)); @@ -301,7 +301,7 @@ void main() { ), ); - await tester.drag(find.text('9'), const Offset(0.0, 32.0)); + await tester.drag(find.text('9'), const Offset(0.0, 32.0), touchSlopY: 0); await tester.pump(); await tester.pump(const Duration(milliseconds: 500)); @@ -487,7 +487,8 @@ void main() { ), ); - await tester.drag(find.text('March'), const Offset(0.0, 32.0)); + await tester.drag(find.text('March'), const Offset(0, 32.0), touchSlopY: 0.0); + // Momentarily, the 2018 and the incorrect 30 of February is aligned. expect( tester.getTopLeft(find.text('2018')).dy, @@ -525,15 +526,14 @@ void main() { ), ); - await tester.drag(find.text('27'), const Offset(0.0, -32.0)); + await tester.drag(find.text('27'), const Offset(0.0, -32.0), touchSlopY: 0.0); await tester.pump(); expect( date, DateTime(2018, 2, 28), ); - - await tester.drag(find.text('28'), const Offset(0.0, -32.0)); + await tester.drag(find.text('28'), const Offset(0.0, -32.0), touchSlopY: 0.0); await tester.pump(); // Once to trigger the post frame animate call. // Callback doesn't transiently go into invalid dates. @@ -706,6 +706,8 @@ void main() { ), ); + const Offset deltaOffset = Offset(0.0, -18.0); + // 11:59 -> 12:59 await tester.drag(find.text('11'), _kRowOffset); await tester.pump(); @@ -721,14 +723,14 @@ void main() { expect(date, DateTime(2018, 1, 1, 11, 59)); // 11:59 -> 9:59 - await tester.drag(find.text('11'), -_kRowOffset * 2); + await tester.drag(find.text('11'), -((_kRowOffset - deltaOffset) * 2 + deltaOffset)); await tester.pump(); await tester.pump(const Duration(milliseconds: 500)); expect(date, DateTime(2018, 1, 1, 9, 59)); // 9:59 -> 15:59 - await tester.drag(find.text('9'), _kRowOffset * 6); + await tester.drag(find.text('9'), (_kRowOffset - deltaOffset) * 6 + deltaOffset); await tester.pump(); await tester.pump(const Duration(milliseconds: 500)); diff --git a/packages/flutter/test/cupertino/picker_test.dart b/packages/flutter/test/cupertino/picker_test.dart index cf410d4f4a8..81021bfad48 100644 --- a/packages/flutter/test/cupertino/picker_test.dart +++ b/packages/flutter/test/cupertino/picker_test.dart @@ -298,7 +298,7 @@ void main() { ); // Drag it by a bit but not enough to move to the next item. - await tester.drag(find.text('10'), const Offset(0.0, 30.0)); + await tester.drag(find.text('10'), const Offset(0.0, 30.0), touchSlopY: 0.0); // The item that was in the center now moved a bit. expect( @@ -315,7 +315,7 @@ void main() { expect(selectedItems.isEmpty, true); // Drag it by enough to move to the next item. - await tester.drag(find.text('10'), const Offset(0.0, 70.0)); + await tester.drag(find.text('10'), const Offset(0.0, 70.0), touchSlopY: 0.0); await tester.pumpAndSettle(); diff --git a/packages/flutter/test/cupertino/refresh_test.dart b/packages/flutter/test/cupertino/refresh_test.dart index 590ad579f3a..115b3a4f17f 100644 --- a/packages/flutter/test/cupertino/refresh_test.dart +++ b/packages/flutter/test/cupertino/refresh_test.dart @@ -117,7 +117,7 @@ void main() { ); // Drag down but not enough to trigger the refresh. - await tester.drag(find.text('0'), const Offset(0.0, 50.0)); + await tester.drag(find.text('0'), const Offset(0.0, 50.0), touchSlopY: 0); await tester.pump(); // The function is referenced once while passing into CupertinoSliverRefreshControl @@ -191,7 +191,7 @@ void main() { ); // Drag down but not enough to trigger the refresh. - await tester.drag(find.text('0'), const Offset(0.0, 50.0)); + await tester.drag(find.text('0'), const Offset(0.0, 50.0), touchSlopY: 0); await tester.pump(); await tester.pump(const Duration(milliseconds: 20)); await tester.pump(const Duration(milliseconds: 20)); @@ -317,7 +317,7 @@ void main() { ), ); - await tester.drag(find.text('0'), const Offset(0.0, 150.0)); + await tester.drag(find.text('0'), const Offset(0.0, 150.0), touchSlopY: 0); await tester.pump(); // Let it start snapping back. await tester.pump(const Duration(milliseconds: 50)); @@ -394,7 +394,7 @@ void main() { ), ); - await tester.drag(find.text('0'), const Offset(0.0, 150.0)); + await tester.drag(find.text('0'), const Offset(0.0, 150.0), touchSlopY: 0); await tester.pump(); verify(mockHelper.builder( @@ -411,7 +411,7 @@ void main() { Rect.fromLTRB(0.0, 0.0, 800.0, 150.0), ); - await tester.drag(find.text('0'), const Offset(0.0, -300.0)); + await tester.drag(find.text('0'), const Offset(0.0, -300.0), touchSlopY: 0); await tester.pump(); // Refresh indicator still being told to layout the same way. @@ -474,7 +474,7 @@ void main() { ), ); - await tester.drag(find.text('0'), const Offset(0.0, 150.0)); + await tester.drag(find.text('0'), const Offset(0.0, 150.0), touchSlopY: 0); await tester.pump(); verify(mockHelper.builder( any, @@ -549,7 +549,7 @@ void main() { ), ); - await tester.drag(find.text('0'), const Offset(0.0, 150.0)); + await tester.drag(find.text('0'), const Offset(0.0, 150.0), touchSlopY: 0); await tester.pump(); verify(mockHelper.builder( any, @@ -639,7 +639,7 @@ void main() { ), ); - await tester.drag(find.text('0'), const Offset(0.0, 150.0)); + await tester.drag(find.text('0'), const Offset(0.0, 150.0), touchSlopY: 0.0); await tester.pump(); verify(mockHelper.refreshTask()); @@ -670,7 +670,7 @@ void main() { // Start another drag by an amount that would have been enough to // trigger another refresh if it were in the right state. - await tester.drag(find.text('0'), const Offset(0.0, 150.0)); + await tester.drag(find.text('0'), const Offset(0.0, 150.0), touchSlopY: 0.0); await tester.pump(); // Instead, it's still in the done state because the sliver never @@ -692,7 +692,7 @@ void main() { ); // Start another drag. It's now in drag mode. - await tester.drag(find.text('0'), const Offset(0.0, 40.0)); + await tester.drag(find.text('0'), const Offset(0.0, 40.0), touchSlopY: 0.0); await tester.pump(); verify(mockHelper.builder( any, @@ -908,7 +908,7 @@ void main() { ), ); - await tester.drag(find.text('0'), const Offset(0.0, 150.0)); + await tester.drag(find.text('0'), const Offset(0.0, 150.0), touchSlopY: 0.0); await tester.pump(); verify(mockHelper.builder( any, @@ -1101,7 +1101,7 @@ void main() { ), ); - await tester.drag(find.text('0'), const Offset(0.0, 100.0)); + await tester.drag(find.text('0'), const Offset(0.0, 100.0), touchSlopY: 0.0); await tester.pump(); expect( CupertinoSliverRefreshControl.state(tester.element(find.byType(LayoutBuilder))), diff --git a/packages/flutter/test/cupertino/switch_test.dart b/packages/flutter/test/cupertino/switch_test.dart index 44926724f5d..6bdb36d5db9 100644 --- a/packages/flutter/test/cupertino/switch_test.dart +++ b/packages/flutter/test/cupertino/switch_test.dart @@ -255,7 +255,6 @@ void main() { return Center( child: CupertinoSwitch( value: value, - dragStartBehavior: DragStartBehavior.down, onChanged: (bool newValue) { setState(() { value = newValue; @@ -270,21 +269,21 @@ void main() { expect(value, isFalse); - await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0)); + await tester.drag(find.byType(CupertinoSwitch), const Offset(-48.0, 0.0)); expect(value, isFalse); - await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0)); + await tester.drag(find.byType(CupertinoSwitch), const Offset(48.0, 0.0)); expect(value, isTrue); await tester.pump(); - await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0)); + await tester.drag(find.byType(CupertinoSwitch), const Offset(48.0, 0.0)); expect(value, isTrue); await tester.pump(); - await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0)); + await tester.drag(find.byType(CupertinoSwitch), const Offset(-48.0, 0.0)); expect(value, isFalse); }); diff --git a/packages/flutter/test/gestures/drag_test.dart b/packages/flutter/test/gestures/drag_test.dart index 3568affa47f..08f817b111f 100644 --- a/packages/flutter/test/gestures/drag_test.dart +++ b/packages/flutter/test/gestures/drag_test.dart @@ -58,9 +58,6 @@ void main() { tester.route(pointer.move(const Offset(20.0, 30.0))); // moved 10 horizontally and 20 vertically which is 22 total expect(didStartPan, isTrue); // 22 > 18 didStartPan = false; - // TODO(jslavitz): revert this testing change. - expect(updatedScrollDelta, const Offset(10.0, 20.0)); - updatedScrollDelta = null; expect(didEndPan, isFalse); expect(didTap, isFalse); @@ -82,6 +79,58 @@ void main() { tap.dispose(); }); + testGesture('Should report most recent point to onStart by default', (GestureTester tester) { + final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); + final VerticalDragGestureRecognizer competingDrag = VerticalDragGestureRecognizer(); + + Offset positionAtOnStart; + drag.onStart = (DragStartDetails details) { + positionAtOnStart = details.globalPosition; + }; + + final TestPointer pointer = TestPointer(5); + final PointerDownEvent down = pointer.down(const Offset(10.0, 10.0)); + drag.addPointer(down); + competingDrag.addPointer(down); + tester.closeArena(5); + tester.route(down); + + tester.route(pointer.move(const Offset(30.0, 0.0))); + drag.dispose(); + competingDrag.dispose(); + + expect(positionAtOnStart, const Offset(30.0, 00.0)); + }); + + testGesture('Should report most recent point to onStart with a start configuration', (GestureTester tester) { + final HorizontalDragGestureRecognizer drag = + HorizontalDragGestureRecognizer(); + final VerticalDragGestureRecognizer competingDrag = VerticalDragGestureRecognizer(); + + Offset positionAtOnStart; + drag.onStart = (DragStartDetails details) { + positionAtOnStart = details.globalPosition; + }; + Offset updateOffset; + drag.onUpdate = (DragUpdateDetails details) { + updateOffset = details.globalPosition; + }; + + final TestPointer pointer = TestPointer(5); + final PointerDownEvent down = pointer.down(const Offset(10.0, 10.0)); + drag.addPointer(down); + competingDrag.addPointer(down); + tester.closeArena(5); + tester.route(down); + + tester.route(pointer.move(const Offset(30.0, 0.0))); + drag.dispose(); + competingDrag.dispose(); + + expect(positionAtOnStart, const Offset(30.0, 0.0)); + expect(updateOffset, null); + }); + testGesture('Should recognize drag', (GestureTester tester) { final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; diff --git a/packages/flutter/test/widgets/gesture_detector_test.dart b/packages/flutter/test/widgets/gesture_detector_test.dart index 714864b46bc..71b801bd007 100644 --- a/packages/flutter/test/widgets/gesture_detector_test.dart +++ b/packages/flutter/test/widgets/gesture_detector_test.dart @@ -102,7 +102,7 @@ void main() { didStartPan = true; }, onPanUpdate: (DragUpdateDetails details) { - panDelta = details.delta; + panDelta = panDelta == null ? details.delta : panDelta + details.delta; }, onPanEnd: (DragEndDetails details) { didEndPan = true; diff --git a/packages/flutter/test/widgets/list_view_horizontal_test.dart b/packages/flutter/test/widgets/list_view_horizontal_test.dart index fb425be03cb..6e3174c0dbc 100644 --- a/packages/flutter/test/widgets/list_view_horizontal_test.dart +++ b/packages/flutter/test/widgets/list_view_horizontal_test.dart @@ -35,7 +35,7 @@ void main() { await tester.pumpWidget(buildFrame(textDirection: TextDirection.ltr)); await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('1'), const Offset(-300.0, 0.0)); + await tester.drag(find.text('1'), const Offset(-300.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -10..280 = 1 @@ -52,7 +52,7 @@ void main() { // if item 3 was a bit wider, such that its center was past the 800px mark, this would fail, // because it wouldn't be hit tested when scrolling from its center, as drag() does. await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('3'), const Offset(-290.0, 0.0)); + await tester.drag(find.text('3'), const Offset(-290.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -10..280 = 2 @@ -66,7 +66,7 @@ void main() { expect(find.text('5'), findsNothing); await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('3'), const Offset(0.0, -290.0)); + await tester.drag(find.text('3'), const Offset(0.0, -290.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // unchanged expect(find.text('0'), findsNothing); @@ -77,7 +77,7 @@ void main() { expect(find.text('5'), findsNothing); await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('3'), const Offset(-290.0, 0.0)); + await tester.drag(find.text('3'), const Offset(-290.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -10..280 = 3 @@ -96,7 +96,7 @@ void main() { // to move item 3 entirely off screen therefore takes: // 60 + (290-60)*2 = 520 pixels // plus a couple more to be sure - await tester.drag(find.text('3'), const Offset(-522.0, 0.0)); + await tester.drag(find.text('3'), const Offset(-522.0, 0.0), touchSlopX: 0.0); await tester.pump(); // just after release // screen is 800px wide, and has the following items: // -11..279 = 4 @@ -121,7 +121,7 @@ void main() { await tester.pumpWidget(Container()); await tester.pumpWidget(buildFrame(textDirection: TextDirection.ltr), const Duration(seconds: 1)); - await tester.drag(find.text('2'), const Offset(-280.0, 0.0)); + await tester.drag(find.text('2'), const Offset(-280.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -280..10 = 0 @@ -135,7 +135,7 @@ void main() { expect(find.text('4'), findsNothing); expect(find.text('5'), findsNothing); await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('2'), const Offset(-290.0, 0.0)); + await tester.drag(find.text('2'), const Offset(-290.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -280..10 = 1 @@ -165,7 +165,7 @@ void main() { expect(find.text('4'), findsNothing); expect(find.text('5'), findsNothing); - await tester.drag(find.text('0'), const Offset(300.0, 0.0)); + await tester.drag(find.text('0'), const Offset(300.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -80..210 = 3 @@ -182,7 +182,7 @@ void main() { // if item 3 was a bit wider, such that its center was past the 800px mark, this would fail, // because it wouldn't be hit tested when scrolling from its center, as drag() does. await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('2'), const Offset(290.0, 0.0)); + await tester.drag(find.text('2'), const Offset(290.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -10..280 = 4 @@ -196,7 +196,7 @@ void main() { expect(find.text('5'), findsNothing); await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('2'), const Offset(0.0, 290.0)); + await tester.drag(find.text('2'), const Offset(0.0, 290.0), touchSlopY: 0.0); await tester.pump(const Duration(seconds: 1)); // unchanged expect(find.text('0'), findsNothing); @@ -207,7 +207,7 @@ void main() { expect(find.text('5'), findsNothing); await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('3'), const Offset(290.0, 0.0)); + await tester.drag(find.text('3'), const Offset(290.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -10..280 = 5 @@ -226,7 +226,7 @@ void main() { // to move item 3 entirely off screen therefore takes: // 60 + (290-60)*2 = 520 pixels // plus a couple more to be sure - await tester.drag(find.text('4'), const Offset(522.0, 0.0)); + await tester.drag(find.text('4'), const Offset(522.0, 0.0), touchSlopX: 0.0); await tester.pump(); // just after release // screen is 800px wide, and has the following items: // 280..570 = 5 @@ -265,7 +265,7 @@ void main() { expect(find.text('4'), findsNothing); expect(find.text('5'), findsNothing); - await tester.drag(find.text('0'), const Offset(300.0, 0.0)); + await tester.drag(find.text('0'), const Offset(300.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -80..210 = 3 @@ -282,7 +282,7 @@ void main() { // if item 3 was a bit wider, such that its center was past the 800px mark, this would fail, // because it wouldn't be hit tested when scrolling from its center, as drag() does. await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('2'), const Offset(290.0, 0.0)); + await tester.drag(find.text('2'), const Offset(290.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -10..280 = 4 @@ -296,7 +296,7 @@ void main() { expect(find.text('5'), findsNothing); await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('2'), const Offset(0.0, 290.0)); + await tester.drag(find.text('2'), const Offset(0.0, 290.0), touchSlopY: 0.0); await tester.pump(const Duration(seconds: 1)); // unchanged expect(find.text('0'), findsNothing); @@ -307,7 +307,7 @@ void main() { expect(find.text('5'), findsNothing); await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('3'), const Offset(290.0, 0.0)); + await tester.drag(find.text('3'), const Offset(290.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -10..280 = 5 @@ -326,7 +326,7 @@ void main() { // to move item 3 entirely off screen therefore takes: // 60 + (290-60)*2 = 520 pixels // plus a couple more to be sure - await tester.drag(find.text('4'), const Offset(522.0, 0.0)); + await tester.drag(find.text('4'), const Offset(522.0, 0.0), touchSlopX: 0.0); await tester.pump(); // just after release // screen is 800px wide, and has the following items: // 280..570 = 5 @@ -354,7 +354,7 @@ void main() { await tester.pumpWidget(buildFrame(reverse: true, textDirection: TextDirection.rtl)); await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('1'), const Offset(-300.0, 0.0)); + await tester.drag(find.text('1'), const Offset(-300.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -10..280 = 1 @@ -371,7 +371,7 @@ void main() { // if item 3 was a bit wider, such that its center was past the 800px mark, this would fail, // because it wouldn't be hit tested when scrolling from its center, as drag() does. await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('3'), const Offset(-290.0, 0.0)); + await tester.drag(find.text('3'), const Offset(-290.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -10..280 = 2 @@ -385,7 +385,7 @@ void main() { expect(find.text('5'), findsNothing); await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('3'), const Offset(0.0, -290.0)); + await tester.drag(find.text('3'), const Offset(0.0, -290.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // unchanged expect(find.text('0'), findsNothing); @@ -396,7 +396,7 @@ void main() { expect(find.text('5'), findsNothing); await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('3'), const Offset(-290.0, 0.0)); + await tester.drag(find.text('3'), const Offset(-290.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -10..280 = 3 @@ -415,7 +415,7 @@ void main() { // to move item 3 entirely off screen therefore takes: // 60 + (290-60)*2 = 520 pixels // plus a couple more to be sure - await tester.drag(find.text('3'), const Offset(-522.0, 0.0)); + await tester.drag(find.text('3'), const Offset(-522.0, 0.0), touchSlopX: 0.0); await tester.pump(); // just after release // screen is 800px wide, and has the following items: // -11..279 = 4 @@ -440,7 +440,7 @@ void main() { await tester.pumpWidget(Container()); await tester.pumpWidget(buildFrame(reverse: true, textDirection: TextDirection.rtl), const Duration(seconds: 1)); - await tester.drag(find.text('2'), const Offset(-280.0, 0.0)); + await tester.drag(find.text('2'), const Offset(-280.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -280..10 = 0 @@ -454,7 +454,7 @@ void main() { expect(find.text('4'), findsNothing); expect(find.text('5'), findsNothing); await tester.pump(const Duration(seconds: 1)); - await tester.drag(find.text('2'), const Offset(-290.0, 0.0)); + await tester.drag(find.text('2'), const Offset(-290.0, 0.0), touchSlopX: 0.0); await tester.pump(const Duration(seconds: 1)); // screen is 800px wide, and has the following items: // -280..10 = 1 diff --git a/packages/flutter/test/widgets/scroll_controller_test.dart b/packages/flutter/test/widgets/scroll_controller_test.dart index c408add4c6a..5cea44d9cc1 100644 --- a/packages/flutter/test/widgets/scroll_controller_test.dart +++ b/packages/flutter/test/widgets/scroll_controller_test.dart @@ -301,7 +301,7 @@ void main() { await tester.drag(find.byType(ListView), const Offset(0.0, -250.0)); - expect(log, equals([ 250.0 ])); + expect(log, equals([ 20.0, 250.0 ])); log.clear(); controller.dispose(); diff --git a/packages/flutter/test/widgets/scrollable_test.dart b/packages/flutter/test/widgets/scrollable_test.dart index 8b4cf1cec97..83b69cf14a9 100644 --- a/packages/flutter/test/widgets/scrollable_test.dart +++ b/packages/flutter/test/widgets/scrollable_test.dart @@ -65,7 +65,7 @@ void main() { testWidgets('Holding scroll', (WidgetTester tester) async { await pumpTest(tester, TargetPlatform.iOS); - await tester.drag(find.byType(Viewport), const Offset(0.0, 200.0)); + await tester.drag(find.byType(Viewport), const Offset(0.0, 200.0), touchSlopY: 0.0); expect(getScrollOffset(tester), -200.0); await tester.pump(); // trigger ballistic await tester.pump(const Duration(milliseconds: 10)); diff --git a/packages/flutter_test/lib/src/controller.dart b/packages/flutter_test/lib/src/controller.dart index 6e1141a1cee..34804f7d3d0 100644 --- a/packages/flutter_test/lib/src/controller.dart +++ b/packages/flutter_test/lib/src/controller.dart @@ -13,6 +13,12 @@ import 'finders.dart'; import 'test_async_utils.dart'; import 'test_pointer.dart'; +/// The default drag touch slop used to break up a large drag into multiple +/// smaller moves. +/// +/// This value must be greater than [kTouchSlop]. +const double kDragSlopDefault = 20.0; + /// Class that programmatically interacts with widgets. /// /// For a variant of this class suited specifically for unit tests, see @@ -410,8 +416,27 @@ abstract class WidgetController { /// /// If you want the drag to end with a speed so that the gesture recognition /// system identifies the gesture as a fling, consider using [fling] instead. - Future drag(Finder finder, Offset offset, {int pointer}) { - return dragFrom(getCenter(finder), offset, pointer: pointer); + /// + /// {@template flutter.flutter_test.drag} + /// By default, if the x or y component of offset is greater than [kTouchSlop], the + /// gesture is broken up into two separate moves calls. Changing 'touchSlopX' or + /// `touchSlopY` will change the minimum amount of movement in the respective axis + /// before the drag will be broken into multiple calls. To always send the + /// drag with just a single call to [TestGesture.moveBy], `touchSlopX` and `touchSlopY` + /// should be set to 0. + /// + /// Breaking the drag into multiple moves is necessary for accurate execution + /// of drag update calls with a [DragStartBehavior] variable set to + /// [DragStartBehavior.start]. Without such a change, the dragUpdate callback + /// from a drag recognizer will never be invoked. + /// + /// To force this function to a send a single move event, the 'touchSlopX' and + /// 'touchSlopY' variables should be set to 0. However, generally, these values + /// should be left to their default values. + /// {@end template} + Future drag(Finder finder, Offset offset, { int pointer, double touchSlopX = kDragSlopDefault, double touchSlopY = kDragSlopDefault }) { + assert(kDragSlopDefault > kTouchSlop); + return dragFrom(getCenter(finder), offset, pointer: pointer, touchSlopX: touchSlopX, touchSlopY: touchSlopY); } /// Attempts a drag gesture consisting of a pointer down, a move by @@ -420,11 +445,78 @@ abstract class WidgetController { /// If you want the drag to end with a speed so that the gesture recognition /// system identifies the gesture as a fling, consider using [flingFrom] /// instead. - Future dragFrom(Offset startLocation, Offset offset, {int pointer}) { + /// + /// {@macro flutter.flutter_test.drag} + Future dragFrom(Offset startLocation, Offset offset, { int pointer, double touchSlopX = kDragSlopDefault, double touchSlopY = kDragSlopDefault }) { + assert(kDragSlopDefault > kTouchSlop); return TestAsyncUtils.guard(() async { final TestGesture gesture = await startGesture(startLocation, pointer: pointer); assert(gesture != null); - await gesture.moveBy(offset); + + final double xSign = offset.dx.sign; + final double ySign = offset.dy.sign; + + final double offsetX = offset.dx; + final double offsetY = offset.dy; + + final bool separateX = offset.dx.abs() > touchSlopX && touchSlopX > 0; + final bool separateY = offset.dy.abs() > touchSlopY && touchSlopY > 0; + + if (separateY || separateX) { + final double offsetSlope = offsetY / offsetX; + final double inverseOffsetSlope = offsetX / offsetY; + final double slopSlope = touchSlopY / touchSlopX; + final double absoluteOffsetSlope = offsetSlope.abs(); + final double signedSlopX = touchSlopX * xSign; + final double signedSlopY = touchSlopY * ySign; + if (absoluteOffsetSlope != slopSlope) { + // The drag goes through one or both of the extents of the edges of the box. + if (absoluteOffsetSlope < slopSlope) { + assert(offsetX.abs() > touchSlopX); + // The drag goes through the vertical edge of the box. + // It is guaranteed that the |offsetX| > touchSlopX. + final double diffY = offsetSlope.abs() * touchSlopX * ySign; + + // The vector from the origin to the vertical edge. + await gesture.moveBy(Offset(signedSlopX, diffY)); + if (offsetY.abs() <= touchSlopY) { + // The drag ends on or before getting to the horizontal extension of the horizontal edge. + await gesture.moveBy(Offset(offsetX - signedSlopX, offsetY - diffY)); + } else { + final double diffY2 = signedSlopY - diffY; + final double diffX2 = inverseOffsetSlope * diffY2; + + // The vector from the edge of the box to the horizontal extension of the horizontal edge. + await gesture.moveBy(Offset(diffX2, diffY2)); + await gesture.moveBy(Offset(offsetX - diffX2 - signedSlopX, offsetY - signedSlopY)); + } + } else { + assert(offsetY.abs() > touchSlopY); + // The drag goes through the horizontal edge of the box. + // It is guaranteed that the |offsetY| > touchSlopY. + final double diffX = inverseOffsetSlope.abs() * touchSlopY * xSign; + + // The vector from the origin to the vertical edge. + await gesture.moveBy(Offset(diffX, signedSlopY)); + if (offsetX.abs() <= touchSlopX) { + // The drag ends on or before getting to the vertical extension of the vertical edge. + await gesture.moveBy(Offset(offsetX - diffX, offsetY - signedSlopY)); + } else { + final double diffX2 = signedSlopX - diffX; + final double diffY2 = offsetSlope * diffX2; + + // The vector from the edge of the box to the vertical extension of the vertical edge. + await gesture.moveBy(Offset(diffX2, diffY2)); + await gesture.moveBy(Offset(offsetX - signedSlopX, offsetY - diffY2 - signedSlopY)); + } + } + } else { // The drag goes through the corner of the box. + await gesture.moveBy(Offset(signedSlopX, signedSlopY)); + await gesture.moveBy(Offset(offsetX - signedSlopX, offsetY - signedSlopY)); + } + } else { // The drag ends inside the box. + await gesture.moveBy(offset); + } await gesture.up(); }); } diff --git a/packages/flutter_test/test/controller_test.dart b/packages/flutter_test/test/controller_test.dart new file mode 100644 index 00000000000..7a30650a3ef --- /dev/null +++ b/packages/flutter_test/test/controller_test.dart @@ -0,0 +1,282 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/gestures.dart'; + +void main() { + testWidgets( + 'WidgetTester.drag must break the offset into multiple parallel components if' + 'the drag goes outside the touch slop values', + (WidgetTester tester) async { + // The first Offset in every sub array (ie. offsetResults[i][0]) is (touchSlopX, touchSlopY). + // The second Offset in every sub array (ie. offsetResults[i][1]) will be the total move offset. + // The remaining values in every sub array are the expected separated drag offsets. + + // This test checks to make sure that the total drag will be correctly split into + // pieces such that the first (and potentially second) moveBy function call(s) in + // controller.drag() will never have a component greater than the touch + // slop in that component's respective axis. + final List> offsetResults = >[ + [ + const Offset(10.0, 10.0), + const Offset(-150.0, 200.0), + const Offset(-7.5, 10.0), + const Offset(-2.5, 3.333333333333333), + const Offset(-140.0, 186.66666666666666), + ], + [ + const Offset(10.0, 10.0), + const Offset(150, -200), + const Offset(7.5, -10), + const Offset(2.5, -3.333333333333333), + const Offset(140.0, -186.66666666666666), + ], + [ + const Offset(10.0, 10.0), + const Offset(-200, 150), + const Offset(-10, 7.5), + const Offset(-3.333333333333333, 2.5), + const Offset(-186.66666666666666, 140.0), + ], + [ + const Offset(10.0, 10.0), + const Offset(200.0, -150.0), + const Offset(10, -7.5), + const Offset(3.333333333333333, -2.5), + const Offset(186.66666666666666, -140.0), + ], + [ + const Offset(10.0, 10.0), + const Offset(-150.0, -200.0), + const Offset(-7.5, -10.0), + const Offset(-2.5, -3.333333333333333), + const Offset(-140.0, -186.66666666666666), + ], + [ + const Offset(10.0, 10.0), + const Offset(8.0, 3.0), + const Offset(8.0, 3.0), + ], + [ + const Offset(10.0, 10.0), + const Offset(3.0, 8.0), + const Offset(3.0, 8.0), + ], + [ + const Offset(10.0, 10.0), + const Offset(20.0, 5.0), + const Offset(10.0, 2.5), + const Offset(10.0, 2.5), + ], + [ + const Offset(10.0, 10.0), + const Offset(5.0, 20.0), + const Offset(2.5, 10.0), + const Offset(2.5, 10.0), + ], + [ + const Offset(10.0, 10.0), + const Offset(20.0, 15.0), + const Offset(10.0, 7.5), + const Offset(3.333333333333333, 2.5), + const Offset(6.666666666666668, 5.0), + ], + [ + const Offset(10.0, 10.0), + const Offset(15.0, 20.0), + const Offset(7.5, 10.0), + const Offset(2.5, 3.333333333333333), + const Offset(5.0, 6.666666666666668), + ], + [ + const Offset(10.0, 10.0), + const Offset(20.0, 20.0), + const Offset(10.0, 10.0), + const Offset(10.0, 10.0), + ], + [ + const Offset(10.0, 10.0), + const Offset(0.0, 5.0), + const Offset(0.0, 5.0), + ], + + //// [VARYING TOUCH SLOP] //// + [ + const Offset(12.0, 5.0), + const Offset(0.0, 5.0), + const Offset(0.0, 5.0), + ], + [ + const Offset(12.0, 5.0), + const Offset(20.0, 5.0), + const Offset(12.0, 3.0), + const Offset(8.0, 2.0), + ], + [ + const Offset(12.0, 5.0), + const Offset(5.0, 20.0), + const Offset(1.25, 5.0), + const Offset(3.75, 15.0), + ], + [ + const Offset(5.0, 12.0), + const Offset(5.0, 20.0), + const Offset(3.0, 12.0), + const Offset(2.0, 8.0), + ], + [ + const Offset(5.0, 12.0), + const Offset(20.0, 5.0), + const Offset(5.0, 1.25), + const Offset(15.0, 3.75), + ], + [ + const Offset(18.0, 18.0), + const Offset(0.0, 150.0), + const Offset(0.0, 18.0), + const Offset(0.0, 132.0), + ], + [ + const Offset(18.0, 18.0), + const Offset(0.0, -150.0), + const Offset(0.0, -18.0), + const Offset(0.0, -132.0), + ], + [ + const Offset(18.0, 18.0), + const Offset(-150.0, 0.0), + const Offset(-18.0, 0.0), + const Offset(-132.0, 0.0), + ], + [ + const Offset(0.0, 0.0), + const Offset(-150.0, 0.0), + const Offset(-150.0, 0.0), + ], + [ + const Offset(18.0, 18.0), + const Offset(-32.0, 0.0), + const Offset(-18.0, 0.0), + const Offset(-14.0, 0.0), + ], + ]; + + await tester.pumpWidget( + const Directionality( + textDirection: TextDirection.ltr, + child: Text('test'), + ), + ); + + final WidgetControllerSpy spyController = WidgetControllerSpy(tester.binding); + + for (int resultIndex = 0; resultIndex < offsetResults.length; resultIndex += 1) { + final List testResult = offsetResults[resultIndex]; + await spyController.drag( + find.text('test'), + testResult[1], + touchSlopX: testResult[0].dx, + touchSlopY: testResult[0].dy, + ); + final List dragOffsets = spyController.testGestureSpy.offsets; + expect( + offsetResults[resultIndex].length - 2, + dragOffsets.length, + + reason: + 'There is a difference in the number of expected and actual split offsets for the drag with:\n' + 'Touch Slop: ' + testResult[0].toString() + '\n' + 'Delta: ' + testResult[1].toString() + '\n', + ); + for (int valueIndex = 2; valueIndex < offsetResults[resultIndex].length; valueIndex += 1) { + expect( + offsetResults[resultIndex][valueIndex], + dragOffsets[valueIndex - 2], + reason: + 'There is a difference in the expected and actual value of the ' + + (valueIndex == 2 ? 'first' : valueIndex == 3 ? 'second' : 'third') + + ' split offset for the drag with:\n' + 'Touch slop: ' + testResult[0].toString() + '\n' + 'Delta: ' + testResult[1].toString() + '\n', + ); + } + spyController.testGestureSpy.clearOffsets(); + } + }, + ); +} + +class WidgetControllerSpy extends WidgetController { + WidgetControllerSpy( + TestWidgetsFlutterBinding binding + ) : super(binding) { + _binding = binding; + } + + TestWidgetsFlutterBinding _binding; + + @override + Future pump(Duration duration) async { + if (duration != null) + await Future.delayed(duration); + _binding.scheduleFrame(); + await _binding.endOfFrame; + } + + int _getNextPointer() { + final int result = nextPointer; + nextPointer += 1; + return result; + } + + @override + Future sendEventToBinding(PointerEvent event, HitTestResult result) { + return TestAsyncUtils.guard(() async { + _binding.dispatchEvent(event, result, source: TestBindingEventSource.test); + }); + } + + TestGestureSpy testGestureSpy; + + @override + Future createGesture({int pointer, PointerDeviceKind kind = PointerDeviceKind.touch}) async { + return testGestureSpy = TestGestureSpy( + pointer: pointer ?? _getNextPointer(), + kind: kind, + dispatcher: sendEventToBinding, + hitTester: hitTestOnBinding + ); + } +} + +class TestGestureSpy extends TestGesture { + TestGestureSpy({ + int pointer, + PointerDeviceKind kind, + EventDispatcher dispatcher, + HitTester hitTester + }) : super( + pointer: pointer, + kind: kind, + dispatcher: dispatcher, + hitTester: hitTester + ); + + List offsets = []; + + void clearOffsets() { + offsets = []; + } + + @override + Future moveBy(Offset offset, {Duration timeStamp = Duration.zero}) { + offsets.add(offset); + return super.moveBy(offset, timeStamp: timeStamp); + } +}