diff --git a/examples/flutter_gallery/lib/demo/material/drawer_demo.dart b/examples/flutter_gallery/lib/demo/material/drawer_demo.dart index 3f08bdea83..a97819910f 100644 --- a/examples/flutter_gallery/lib/demo/material/drawer_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/drawer_demo.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import '../../gallery/demo.dart'; @@ -79,6 +80,7 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { return Scaffold( + drawerDragStartBehavior: DragStartBehavior.down, key: _scaffoldKey, appBar: AppBar( leading: IconButton( @@ -106,6 +108,7 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { ), otherAccountsPictures: [ GestureDetector( + dragStartBehavior: DragStartBehavior.down, onTap: () { _onOtherAccountsTap(context); }, @@ -120,6 +123,7 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { ), ), GestureDetector( + dragStartBehavior: DragStartBehavior.down, onTap: () { _onOtherAccountsTap(context); }, @@ -149,6 +153,7 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { removeTop: true, child: Expanded( child: ListView( + dragStartBehavior: DragStartBehavior.down, padding: const EdgeInsets.only(top: 8.0), children: [ Stack( diff --git a/examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart b/examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart index ddbea4bc54..4deab75ab4 100644 --- a/examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import '../../gallery/demo.dart'; @@ -67,6 +68,7 @@ class _PasswordFieldState extends State { labelText: widget.labelText, helperText: widget.helperText, suffixIcon: GestureDetector( + dragStartBehavior: DragStartBehavior.down, onTap: () { setState(() { _obscureText = !_obscureText; @@ -167,6 +169,7 @@ class TextFormFieldDemoState extends State { @override Widget build(BuildContext context) { return Scaffold( + drawerDragStartBehavior: DragStartBehavior.down, key: _scaffoldKey, appBar: AppBar( title: const Text('Text fields'), @@ -180,6 +183,7 @@ class TextFormFieldDemoState extends State { autovalidate: _autovalidate, onWillPop: _warnUserAboutInvalidData, child: SingleChildScrollView( + dragStartBehavior: DragStartBehavior.down, padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart index c77134ea0a..f13b51b570 100644 --- a/examples/stocks/lib/stock_home.dart +++ b/examples/stocks/lib/stock_home.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart' show debugDumpRenderTree, debugDumpLayerTree, debugDumpSemanticsTree, DebugSemanticsDumpOrder; import 'package:flutter/scheduler.dart' show timeDilation; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'stock_data.dart'; import 'stock_list.dart'; import 'stock_strings.dart'; @@ -110,6 +111,7 @@ class StockHomeState extends State { Widget _buildDrawer(BuildContext context) { return Drawer( child: ListView( + dragStartBehavior: DragStartBehavior.down, children: [ const DrawerHeader(child: Center(child: Text('Stocks'))), const ListTile( @@ -317,11 +319,13 @@ class StockHomeState extends State { return DefaultTabController( length: 2, child: Scaffold( + drawerDragStartBehavior: DragStartBehavior.down, key: _scaffoldKey, appBar: _isSearching ? buildSearchBar() : buildAppBar(), floatingActionButton: buildFloatingActionButton(), drawer: _buildDrawer(context), body: TabBarView( + dragStartBehavior: DragStartBehavior.down, children: [ _buildStockTab(context, StockHomeTab.market, widget.stocks.allSymbols), _buildStockTab(context, StockHomeTab.portfolio, portfolioSymbols), diff --git a/packages/flutter/lib/src/cupertino/switch.dart b/packages/flutter/lib/src/cupertino/switch.dart index bbb1d7a42f..f32b2d8ada 100644 --- a/packages/flutter/lib/src/cupertino/switch.dart +++ b/packages/flutter/lib/src/cupertino/switch.dart @@ -52,12 +52,16 @@ import 'thumb_painter.dart'; /// * class CupertinoSwitch extends StatefulWidget { /// Creates an iOS-style switch. + /// + /// [dragStartBehavior] must not be null. const CupertinoSwitch({ Key key, @required this.value, @required this.onChanged, this.activeColor, - }) : super(key: key); + this.dragStartBehavior = DragStartBehavior.start, + }) : assert(dragStartBehavior != null), + super(key: key); /// Whether this switch is on or off. final bool value; @@ -92,6 +96,26 @@ class CupertinoSwitch extends StatefulWidget { /// [CupertinoTheme] in accordance to native iOS behavior. final Color activeColor; + /// {@template flutter.cupertino.switch.dragStartBehavior} + /// 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. + /// + /// 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]. + /// + /// See also: + /// + /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. + /// {@endtemplate} + final DragStartBehavior dragStartBehavior; + @override _CupertinoSwitchState createState() => _CupertinoSwitchState(); @@ -111,6 +135,7 @@ class _CupertinoSwitchState extends State with TickerProviderSt activeColor: widget.activeColor ?? CupertinoColors.activeGreen, onChanged: widget.onChanged, vsync: this, + dragStartBehavior: widget.dragStartBehavior, ); } } @@ -122,12 +147,14 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { this.activeColor, this.onChanged, this.vsync, + this.dragStartBehavior = DragStartBehavior.start, }) : super(key: key); final bool value; final Color activeColor; final ValueChanged onChanged; final TickerProvider vsync; + final DragStartBehavior dragStartBehavior; @override _RenderCupertinoSwitch createRenderObject(BuildContext context) { @@ -137,6 +164,7 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { onChanged: onChanged, textDirection: Directionality.of(context), vsync: vsync, + dragStartBehavior: dragStartBehavior ); } @@ -147,7 +175,8 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { ..activeColor = activeColor ..onChanged = onChanged ..textDirection = Directionality.of(context) - ..vsync = vsync; + ..vsync = vsync + ..dragStartBehavior = dragStartBehavior; } } @@ -171,6 +200,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { ValueChanged onChanged, @required TextDirection textDirection, @required TickerProvider vsync, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : assert(value != null), assert(activeColor != null), assert(vsync != null), @@ -188,7 +218,8 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { _drag = HorizontalDragGestureRecognizer() ..onStart = _handleDragStart ..onUpdate = _handleDragUpdate - ..onEnd = _handleDragEnd; + ..onEnd = _handleDragEnd + ..dragStartBehavior = dragStartBehavior; _positionController = AnimationController( duration: _kToggleDuration, value: value ? 1.0 : 0.0, @@ -276,6 +307,14 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { markNeedsPaint(); } + DragStartBehavior get dragStartBehavior => _drag.dragStartBehavior; + set dragStartBehavior(DragStartBehavior value) { + assert(value != null); + if (_drag.dragStartBehavior == value) + return; + _drag.dragStartBehavior = value; + } + bool get isInteractive => onChanged != null; TapGestureRecognizer _tap; diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart index 4c25e6857a..c8233113b2 100644 --- a/packages/flutter/lib/src/gestures/monodrag.dart +++ b/packages/flutter/lib/src/gestures/monodrag.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart'; + import 'arena.dart'; import 'constants.dart'; import 'drag_details.dart'; @@ -48,7 +50,36 @@ typedef GestureDragCancelCallback = void Function(); /// * [PanGestureRecognizer], for drags that are not locked to a single axis. abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { /// Initialize the object. - DragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner); + /// + /// [dragStartBehavior] must not be null. + DragGestureRecognizer({ + Object debugOwner, + this.dragStartBehavior = DragStartBehavior.start, + }) : assert(dragStartBehavior != null), + super(debugOwner: debugOwner); + + /// Configure the behavior of offsets sent to [onStart]. + /// + /// If set to [DragStartBehavior.start], the [onStart] callback will be called at the time and + /// position when the gesture detector wins the arena. If [DragStartBehavior.down], + /// [onStart] will be called at the time and position when a down event was + /// first detected. + /// + /// For more information about the gesture arena: + /// https://flutter.io/docs/development/ui/advanced/gestures#gesture-disambiguation + /// + /// By default, the drag start behavior is [DragStartBehavior.start]. + /// + /// ## 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 [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). + DragStartBehavior dragStartBehavior; /// A pointer has contacted the screen and might begin to move. /// @@ -60,6 +91,11 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { /// /// 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]. GestureDragStartCallback onStart; /// A pointer that is in contact with the screen and moving has moved again. @@ -163,6 +199,16 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { _state = _DragState.accepted; final Offset delta = _pendingDragOffset; final Duration timestamp = _lastPendingEventTimestamp; + Offset updateDelta; + switch (dragStartBehavior) { + case DragStartBehavior.start: + _initialPosition = _initialPosition + delta; + updateDelta = Offset.zero; + break; + case DragStartBehavior.down: + updateDelta = _getDeltaForDetails(delta); + break; + } _pendingDragOffset = Offset.zero; _lastPendingEventTimestamp = null; if (onStart != null) { @@ -171,13 +217,12 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { globalPosition: _initialPosition, ))); } - if (delta != Offset.zero && onUpdate != null) { - final Offset deltaForDetails = _getDeltaForDetails(delta); + if (updateDelta != Offset.zero && onUpdate != null) { invokeCallback('onUpdate', () => onUpdate(DragUpdateDetails( sourceTimeStamp: timestamp, - delta: deltaForDetails, - primaryDelta: _getPrimaryValueFromOffset(delta), - globalPosition: _initialPosition + deltaForDetails, + delta: updateDelta, + primaryDelta: _getPrimaryValueFromOffset(updateDelta), + globalPosition: _initialPosition + updateDelta, // Only adds delta for down behaviour ))); } } @@ -232,6 +277,11 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { _velocityTrackers.clear(); super.dispose(); } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(EnumProperty('Start Behavior', dragStartBehavior)); + } } /// Recognizes movement in the vertical direction. @@ -280,7 +330,8 @@ class VerticalDragGestureRecognizer extends DragGestureRecognizer { /// track each touch point independently. class HorizontalDragGestureRecognizer extends DragGestureRecognizer { /// Create a gesture recognizer for interactions in the horizontal axis. - HorizontalDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner); + HorizontalDragGestureRecognizer({ Object debugOwner }) : + super(debugOwner: debugOwner); @override bool _isFlingGesture(VelocityEstimate estimate) { diff --git a/packages/flutter/lib/src/gestures/recognizer.dart b/packages/flutter/lib/src/gestures/recognizer.dart index ca71a26b72..d5ddbd619e 100644 --- a/packages/flutter/lib/src/gestures/recognizer.dart +++ b/packages/flutter/lib/src/gestures/recognizer.dart @@ -24,6 +24,24 @@ export 'pointer_router.dart' show PointerRouter; /// anonymous functions that return objects of particular types. 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. +enum DragStartBehavior { + /// Set the initial offset, at the position where the first down even was + /// detected. + down, + + /// Set the initial position at the position where the drag start event was + /// detected. + start, +} + /// The base class that all gesture recognizers inherit from. /// /// Provides a basic API that can be used by classes that work with diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index 12261cc4c9..908a43a8ba 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -8,6 +8,7 @@ import 'dart:math' as math; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'button_bar.dart'; import 'button_theme.dart'; @@ -250,10 +251,12 @@ class DayPicker extends StatelessWidget { @required this.lastDate, @required this.displayedMonth, this.selectableDayPredicate, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(selectedDate != null), assert(currentDate != null), assert(onChanged != null), assert(displayedMonth != null), + assert(dragStartBehavior != null), assert(!firstDate.isAfter(lastDate)), assert(selectedDate.isAfter(firstDate) || selectedDate.isAtSameMomentAs(firstDate)), super(key: key); @@ -281,6 +284,13 @@ class DayPicker extends StatelessWidget { /// Optional user supplied predicate function to customize selectable days. final SelectableDayPredicate selectableDayPredicate; + /// The initial drag behavior of the date picker wheel. + /// + /// If set to [DragStartBehavior.start], picker drag behavior will begin upon the + /// drag gesture winning the arena. If set to [DragStartBehavior.down] it will + /// begin when a down event is first detected. + final DragStartBehavior dragStartBehavior; + /// Builds widgets showing abbreviated days of week. The first widget in the /// returned list corresponds to the first day of week for the current locale. /// @@ -442,6 +452,7 @@ class DayPicker extends StatelessWidget { onChanged(dayToBuild); }, child: dayWidget, + dragStartBehavior: dragStartBehavior, ); } @@ -502,6 +513,7 @@ class MonthPicker extends StatefulWidget { @required this.firstDate, @required this.lastDate, this.selectableDayPredicate, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(selectedDate != null), assert(onChanged != null), assert(!firstDate.isAfter(lastDate)), @@ -525,6 +537,9 @@ class MonthPicker extends StatefulWidget { /// Optional user supplied predicate function to customize selectable days. final SelectableDayPredicate selectableDayPredicate; + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + @override _MonthPickerState createState() => _MonthPickerState(); } @@ -609,6 +624,7 @@ class _MonthPickerState extends State with SingleTickerProviderStat lastDate: widget.lastDate, displayedMonth: month, selectableDayPredicate: widget.selectableDayPredicate, + dragStartBehavior: widget.dragStartBehavior, ); } @@ -669,6 +685,7 @@ class _MonthPickerState extends State with SingleTickerProviderStat return false; }, child: PageView.builder( + dragStartBehavior: widget.dragStartBehavior, key: ValueKey(widget.selectedDate), controller: _dayPickerController, scrollDirection: Axis.horizontal, @@ -759,6 +776,7 @@ class YearPicker extends StatefulWidget { @required this.onChanged, @required this.firstDate, @required this.lastDate, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(selectedDate != null), assert(onChanged != null), assert(!firstDate.isAfter(lastDate)), @@ -778,6 +796,9 @@ class YearPicker extends StatefulWidget { /// The latest date the user is permitted to pick. final DateTime lastDate; + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + @override _YearPickerState createState() => _YearPickerState(); } @@ -801,6 +822,7 @@ class _YearPickerState extends State { final ThemeData themeData = Theme.of(context); final TextStyle style = themeData.textTheme.body1; return ListView.builder( + dragStartBehavior: widget.dragStartBehavior, controller: scrollController, itemExtent: _itemExtent, itemCount: widget.lastDate.year - widget.firstDate.year + 1, diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index 89c4e22187..6a818e99d9 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -6,6 +6,7 @@ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'colors.dart'; import 'debug.dart'; @@ -179,7 +180,9 @@ class DrawerController extends StatefulWidget { @required this.child, @required this.alignment, this.drawerCallback, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(child != null), + assert(dragStartBehavior != null), assert(alignment != null), super(key: key); @@ -197,6 +200,26 @@ class DrawerController extends StatefulWidget { /// Optional callback that is called when a [Drawer] is opened or closed. final DrawerCallback drawerCallback; + /// {@template flutter.material.drawer.dragStartBehavior} + /// 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. + /// + /// 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]. + /// + /// See also: + /// + /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. + /// {@endtemplate} + final DragStartBehavior dragStartBehavior; + @override DrawerControllerState createState() => DrawerControllerState(); } @@ -399,6 +422,7 @@ class DrawerControllerState extends State with SingleTickerPro onHorizontalDragEnd: _settle, behavior: HitTestBehavior.translucent, excludeFromSemantics: true, + dragStartBehavior: widget.dragStartBehavior, child: Container(width: dragAreaWidth), ), ); @@ -410,6 +434,7 @@ class DrawerControllerState extends State with SingleTickerPro onHorizontalDragEnd: _settle, onHorizontalDragCancel: _handleDragCancel, excludeFromSemantics: true, + dragStartBehavior: widget.dragStartBehavior, child: RepaintBoundary( child: Stack( children: [ diff --git a/packages/flutter/lib/src/material/paginated_data_table.dart b/packages/flutter/lib/src/material/paginated_data_table.dart index 792eec9e86..cfd0b4c07d 100644 --- a/packages/flutter/lib/src/material/paginated_data_table.dart +++ b/packages/flutter/lib/src/material/paginated_data_table.dart @@ -6,6 +6,7 @@ import 'dart:math' as math; import 'package:flutter/widgets.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'button_bar.dart'; import 'button_theme.dart'; @@ -74,9 +75,11 @@ class PaginatedDataTable extends StatefulWidget { this.rowsPerPage = defaultRowsPerPage, this.availableRowsPerPage = const [defaultRowsPerPage, defaultRowsPerPage * 2, defaultRowsPerPage * 5, defaultRowsPerPage * 10], this.onRowsPerPageChanged, + this.dragStartBehavior = DragStartBehavior.start, @required this.source }) : assert(header != null), assert(columns != null), + assert(dragStartBehavior != null), assert(columns.isNotEmpty), assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)), assert(sortAscending != null), @@ -170,6 +173,9 @@ class PaginatedDataTable extends StatefulWidget { /// [PaginatedDataTable] constructor is called. final DataTableSource source; + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + @override PaginatedDataTableState createState() => PaginatedDataTableState(); } @@ -417,6 +423,7 @@ class PaginatedDataTableState extends State { ), SingleChildScrollView( scrollDirection: Axis.horizontal, + dragStartBehavior: widget.dragStartBehavior, child: DataTable( key: _tableKey, columns: widget.columns, @@ -435,6 +442,7 @@ class PaginatedDataTableState extends State { child: Container( height: 56.0, child: SingleChildScrollView( + dragStartBehavior: widget.dragStartBehavior, scrollDirection: Axis.horizontal, reverse: true, child: Row( diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index baba3ac4e3..7969a86e07 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'app_bar.dart'; import 'bottom_sheet.dart'; @@ -732,7 +733,10 @@ class Scaffold extends StatefulWidget { this.backgroundColor, this.resizeToAvoidBottomPadding = true, this.primary = true, - }) : assert(primary != null), super(key: key); + this.drawerDragStartBehavior = DragStartBehavior.start, + }) : assert(primary != null), + assert(drawerDragStartBehavior != null), + super(key: key); /// An app bar to display at the top of the scaffold. final PreferredSizeWidget appBar; @@ -865,6 +869,9 @@ class Scaffold extends StatefulWidget { /// [AppBar.primary], is true. final bool primary; + /// {@macro flutter.material.drawer.dragStartBehavior} + final DragStartBehavior drawerDragStartBehavior; + /// The state from the closest instance of this class that encloses the given context. /// /// Typical usage is as follows: @@ -1500,6 +1507,7 @@ class ScaffoldState extends State with TickerProviderStateMixin { alignment: DrawerAlignment.end, child: widget.endDrawer, drawerCallback: _endDrawerOpenedCallback, + dragStartBehavior: widget.drawerDragStartBehavior, ), _ScaffoldSlot.endDrawer, // remove the side padding from the side we're not touching @@ -1521,6 +1529,7 @@ class ScaffoldState extends State with TickerProviderStateMixin { alignment: DrawerAlignment.start, child: widget.drawer, drawerCallback: _drawerOpenedCallback, + dragStartBehavior: widget.drawerDragStartBehavior, ), _ScaffoldSlot.drawer, // remove the side padding from the side we're not touching diff --git a/packages/flutter/lib/src/material/switch.dart b/packages/flutter/lib/src/material/switch.dart index 6bc832c640..183d286a60 100644 --- a/packages/flutter/lib/src/material/switch.dart +++ b/packages/flutter/lib/src/material/switch.dart @@ -73,7 +73,9 @@ class Switch extends StatefulWidget { this.activeThumbImage, this.inactiveThumbImage, this.materialTapTargetSize, + this.dragStartBehavior = DragStartBehavior.start, }) : _switchType = _SwitchType.material, + assert(dragStartBehavior != null), super(key: key); /// Creates a [CupertinoSwitch] if the target platform is iOS, creates a @@ -95,6 +97,7 @@ class Switch extends StatefulWidget { this.activeThumbImage, this.inactiveThumbImage, this.materialTapTargetSize, + this.dragStartBehavior = DragStartBehavior.start, }) : _switchType = _SwitchType.adaptive, super(key: key); @@ -174,6 +177,9 @@ class Switch extends StatefulWidget { final _SwitchType _switchType; + /// {@macro flutter.cupertino.switch.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + @override _SwitchState createState() => _SwitchState(); @@ -219,6 +225,7 @@ class _SwitchState extends State with TickerProviderStateMixin { } return _SwitchRenderObjectWidget( + dragStartBehavior: widget.dragStartBehavior, value: widget.value, activeColor: activeThumbColor, inactiveColor: inactiveThumbColor, @@ -240,6 +247,7 @@ class _SwitchState extends State with TickerProviderStateMixin { height: size.height, alignment: Alignment.center, child: CupertinoSwitch( + dragStartBehavior: widget.dragStartBehavior, value: widget.value, onChanged: widget.onChanged, activeColor: widget.activeColor, @@ -284,6 +292,7 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { this.onChanged, this.vsync, this.additionalConstraints, + this.dragStartBehavior, }) : super(key: key); final bool value; @@ -297,10 +306,12 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { final ValueChanged onChanged; final TickerProvider vsync; final BoxConstraints additionalConstraints; + final DragStartBehavior dragStartBehavior; @override _RenderSwitch createRenderObject(BuildContext context) { return _RenderSwitch( + dragStartBehavior: dragStartBehavior, value: value, activeColor: activeColor, inactiveColor: inactiveColor, @@ -330,6 +341,7 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ..onChanged = onChanged ..textDirection = Directionality.of(context) ..additionalConstraints = additionalConstraints + ..dragStartBehavior = dragStartBehavior ..vsync = vsync; } } @@ -348,6 +360,7 @@ class _RenderSwitch extends RenderToggleable { @required TextDirection textDirection, ValueChanged onChanged, @required TickerProvider vsync, + DragStartBehavior dragStartBehavior, }) : assert(textDirection != null), _activeThumbImage = activeThumbImage, _inactiveThumbImage = inactiveThumbImage, @@ -367,7 +380,8 @@ class _RenderSwitch extends RenderToggleable { _drag = HorizontalDragGestureRecognizer() ..onStart = _handleDragStart ..onUpdate = _handleDragUpdate - ..onEnd = _handleDragEnd; + ..onEnd = _handleDragEnd + ..dragStartBehavior = dragStartBehavior; } ImageProvider get activeThumbImage => _activeThumbImage; @@ -428,6 +442,14 @@ class _RenderSwitch extends RenderToggleable { markNeedsPaint(); } + DragStartBehavior get dragStartBehavior => _drag.dragStartBehavior; + set dragStartBehavior(DragStartBehavior value) { + assert(value != null); + if(_drag.dragStartBehavior == value) + return; + _drag.dragStartBehavior = value; + } + @override void detach() { _cachedThumbPainter?.dispose(); diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index f83130e6cf..79405796ed 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -7,6 +7,7 @@ import 'dart:ui' show lerpDouble; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'app_bar.dart'; import 'colors.dart'; @@ -549,9 +550,11 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { this.labelPadding, this.unselectedLabelColor, this.unselectedLabelStyle, + this.dragStartBehavior = DragStartBehavior.start, this.onTap, }) : assert(tabs != null), assert(isScrollable != null), + assert(dragStartBehavior != null), assert(indicator != null || (indicatorWeight != null && indicatorWeight > 0.0)), assert(indicator != null || (indicatorPadding != null)), super(key: key); @@ -662,6 +665,9 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { /// is null then the text style of the theme's body2 definition is used. final TextStyle unselectedLabelStyle; + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + /// An optional callback that's called when the [TabBar] is tapped. /// /// The callback is applied to the index of the tab where the tap occurred. @@ -1011,6 +1017,7 @@ class _TabBarState extends State { if (widget.isScrollable) { _scrollController ??= _TabBarScrollController(this); tabBar = SingleChildScrollView( + dragStartBehavior: widget.dragStartBehavior, scrollDirection: Axis.horizontal, controller: _scrollController, child: tabBar, @@ -1035,7 +1042,10 @@ class TabBarView extends StatefulWidget { @required this.children, this.controller, this.physics, - }) : assert(children != null), super(key: key); + this.dragStartBehavior = DragStartBehavior.start, + }) : assert(children != null), + assert(dragStartBehavior != null), + super(key: key); /// This widget's selection and animation state. /// @@ -1057,6 +1067,9 @@ class TabBarView extends StatefulWidget { /// Defaults to matching platform conventions. final ScrollPhysics physics; + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + @override _TabBarViewState createState() => _TabBarViewState(); } @@ -1201,6 +1214,7 @@ class _TabBarViewState extends State { return NotificationListener( onNotification: _handleScrollNotification, child: PageView( + dragStartBehavior: widget.dragStartBehavior, controller: _pageController, physics: widget.physics == null ? _kTabBarViewPhysics : _kTabBarViewPhysics.applyTo(widget.physics), children: _children, diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 4ca47e4378..d5e374f4e2 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -8,6 +8,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart'; import 'debug.dart'; import 'feedback.dart'; @@ -127,6 +128,7 @@ class TextField extends StatefulWidget { this.cursorColor, this.keyboardAppearance, this.scrollPadding = const EdgeInsets.all(20.0), + this.dragStartBehavior = DragStartBehavior.start, this.enableInteractiveSelection, this.onTap, }) : assert(textAlign != null), @@ -135,6 +137,7 @@ class TextField extends StatefulWidget { assert(autocorrect != null), assert(maxLengthEnforced != null), assert(scrollPadding != null), + assert(dragStartBehavior != null), assert(maxLines == null || maxLines > 0), assert(maxLength == null || maxLength == TextField.noMaxLength || maxLength > 0), keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline), @@ -346,6 +349,9 @@ class TextField extends StatefulWidget { /// {@macro flutter.widgets.editableText.enableInteractiveSelection} final bool enableInteractiveSelection; + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + /// {@macro flutter.rendering.editable.selectionEnabled} bool get selectionEnabled { return enableInteractiveSelection ?? !obscureText; @@ -669,6 +675,7 @@ class _TextFieldState extends State with AutomaticKeepAliveClientMixi scrollPadding: widget.scrollPadding, keyboardAppearance: keyboardAppearance, enableInteractiveSelection: widget.enableInteractiveSelection, + dragStartBehavior: widget.dragStartBehavior, ), ); diff --git a/packages/flutter/lib/src/widgets/dismissible.dart b/packages/flutter/lib/src/widgets/dismissible.dart index f945da48a5..98a94b7f45 100644 --- a/packages/flutter/lib/src/widgets/dismissible.dart +++ b/packages/flutter/lib/src/widgets/dismissible.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/gestures.dart'; + import 'automatic_keep_alive.dart'; import 'basic.dart'; import 'debug.dart'; @@ -82,8 +84,10 @@ class Dismissible extends StatefulWidget { this.dismissThresholds = const {}, this.movementDuration = const Duration(milliseconds: 200), this.crossAxisEndOffset = 0.0, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(key != null), assert(secondaryBackground != null ? background != null : true), + assert(dragStartBehavior != null), super(key: key); /// The widget below this widget in the tree. @@ -142,6 +146,23 @@ class Dismissible extends StatefulWidget { /// it is positive or negative. final double crossAxisEndOffset; + /// 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. + /// + /// 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]. + /// + /// See also: + /// + /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. + final DragStartBehavior dragStartBehavior; + @override _DismissibleState createState() => _DismissibleState(); } @@ -327,8 +348,8 @@ class _DismissibleState extends State with TickerProviderStateMixin Tween( begin: Offset.zero, end: _directionIsXAxis - ? Offset(end, widget.crossAxisEndOffset) - : Offset(widget.crossAxisEndOffset, end), + ? Offset(end, widget.crossAxisEndOffset) + : Offset(widget.crossAxisEndOffset, end), ), ); } @@ -514,7 +535,6 @@ class _DismissibleState extends State with TickerProviderStateMixin children.add(content); content = Stack(children: children); } - // We are not resizing but we may be being dragging in widget.direction. return GestureDetector( onHorizontalDragStart: _directionIsXAxis ? _handleDragStart : null, @@ -524,7 +544,8 @@ class _DismissibleState extends State with TickerProviderStateMixin onVerticalDragUpdate: _directionIsXAxis ? null : _handleDragUpdate, onVerticalDragEnd: _directionIsXAxis ? null : _handleDragEnd, behavior: HitTestBehavior.opaque, - child: content + child: content, + dragStartBehavior: widget.dragStartBehavior, ); } } diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index bd22473fe4..c8dc527481 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -8,6 +8,7 @@ import 'dart:ui' as ui; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'automatic_keep_alive.dart'; import 'basic.dart'; @@ -184,7 +185,8 @@ class EditableText extends StatefulWidget { /// default to [TextInputType.multiline]. /// /// The [controller], [focusNode], [style], [cursorColor], [backgroundCursorColor], - /// [textAlign], and [rendererIgnoresPointer] arguments must not be null. + /// [textAlign], [dragStartBehavior] and [rendererIgnoresPointer] arguments + /// must not be null. EditableText({ Key key, @required this.controller, @@ -215,6 +217,7 @@ class EditableText extends StatefulWidget { this.cursorRadius, this.scrollPadding = const EdgeInsets.all(20.0), this.keyboardAppearance = Brightness.light, + this.dragStartBehavior = DragStartBehavior.start, this.enableInteractiveSelection, }) : assert(controller != null), assert(focusNode != null), @@ -228,6 +231,7 @@ class EditableText extends StatefulWidget { assert(autofocus != null), assert(rendererIgnoresPointer != null), assert(scrollPadding != null), + assert(dragStartBehavior != null), keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline), inputFormatters = maxLines == 1 ? ( @@ -284,6 +288,10 @@ class EditableText extends StatefulWidget { /// its left. /// /// Defaults to the ambient [Directionality], if any. + /// + /// See also: + /// + /// * {@macro flutter.gestures.monodrag.dragStartExample} /// {@endtemplate} final TextDirection textDirection; @@ -494,6 +502,9 @@ class EditableText extends StatefulWidget { /// Defaults to false, resulting in a typical blinking cursor. static bool debugDeterministicCursor = false; + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + /// {@macro flutter.rendering.editable.selectionEnabled} bool get selectionEnabled { return enableInteractiveSelection ?? !obscureText; @@ -840,6 +851,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien renderObject: renderObject, selectionControls: widget.selectionControls, selectionDelegate: this, + dragStartBehavior: widget.dragStartBehavior, ); final bool longPress = cause == SelectionChangedCause.longPress; if (cause != SelectionChangedCause.keyboard && (_value.text.isNotEmpty || longPress)) @@ -1061,6 +1073,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right, controller: _scrollController, physics: const ClampingScrollPhysics(), + dragStartBehavior: widget.dragStartBehavior, viewportBuilder: (BuildContext context, ViewportOffset offset) { return CompositedTransformTarget( link: _layerLink, diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index 158d308b83..de41403c6d 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -184,8 +184,10 @@ class GestureDetector extends StatelessWidget { this.onScaleUpdate, this.onScaleEnd, this.behavior, - this.excludeFromSemantics = false + this.excludeFromSemantics = false, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(excludeFromSemantics != null), + assert(dragStartBehavior != null), assert(() { final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null; final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null; @@ -370,6 +372,23 @@ class GestureDetector extends StatelessWidget { /// 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 upon the detection of a drag gesture. If set to + /// [DragStartBehavior.down] it will begin when 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]. + /// + /// See also: + /// + /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. + final DragStartBehavior dragStartBehavior; + @override Widget build(BuildContext context) { final Map gestures = {}; @@ -421,7 +440,8 @@ class GestureDetector extends StatelessWidget { ..onStart = onVerticalDragStart ..onUpdate = onVerticalDragUpdate ..onEnd = onVerticalDragEnd - ..onCancel = onVerticalDragCancel; + ..onCancel = onVerticalDragCancel + ..dragStartBehavior = dragStartBehavior; }, ); } @@ -439,7 +459,8 @@ class GestureDetector extends StatelessWidget { ..onStart = onHorizontalDragStart ..onUpdate = onHorizontalDragUpdate ..onEnd = onHorizontalDragEnd - ..onCancel = onHorizontalDragCancel; + ..onCancel = onHorizontalDragCancel + ..dragStartBehavior = dragStartBehavior; }, ); } @@ -457,7 +478,8 @@ class GestureDetector extends StatelessWidget { ..onStart = onPanStart ..onUpdate = onPanUpdate ..onEnd = onPanEnd - ..onCancel = onPanCancel; + ..onCancel = onPanCancel + ..dragStartBehavior = dragStartBehavior; }, ); } @@ -497,6 +519,11 @@ class GestureDetector extends StatelessWidget { child: child, ); } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(EnumProperty('startBehavior', dragStartBehavior)); + } } /// A widget that detects gestures described by the given gesture @@ -550,7 +577,7 @@ class RawGestureDetector extends StatefulWidget { this.child, this.gestures = const {}, this.behavior, - this.excludeFromSemantics = false + this.excludeFromSemantics = false, }) : assert(gestures != null), assert(excludeFromSemantics != null), super(key: key); diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index 8d410744a5..4369334f78 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -10,6 +10,7 @@ import 'package:flutter/painting.dart'; import 'package:flutter/physics.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'basic.dart'; import 'framework.dart'; @@ -51,7 +52,7 @@ typedef NestedScrollViewHeaderSliversBuilder = List Function(BuildContex /// in the opposite direction (e.g. allowing the user to swipe horizontally /// between the pages represented by the tabs, while the list scrolls /// vertically), then any list inside that [TabBarView] would not interact with -/// the outer [ScrollView]. For example, flinging the inner list to scroll to +/// the outer [ScrollView]. For example, flinginsg the inner list to scroll to /// the top would not cause a collapsed [SliverAppBar] in the outer [ScrollView] /// to expand. /// @@ -188,6 +189,7 @@ class NestedScrollView extends StatefulWidget { this.physics, @required this.headerSliverBuilder, @required this.body, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(scrollDirection != null), assert(reverse != null), assert(headerSliverBuilder != null), @@ -252,6 +254,9 @@ class NestedScrollView extends StatefulWidget { /// the [PrimaryScrollController] provided by the [NestedScrollView]. final Widget body; + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + /// Returns the [SliverOverlapAbsorberHandle] of the nearest ancestor /// [NestedScrollView]. /// @@ -338,6 +343,7 @@ class _NestedScrollViewState extends State { builder: (BuildContext context) { _lastHasScrolledBody = _coordinator.hasScrolledBody; return _NestedScrollViewCustomScrollView( + dragStartBehavior: widget.dragStartBehavior, scrollDirection: widget.scrollDirection, reverse: widget.reverse, physics: widget.physics != null @@ -365,12 +371,14 @@ class _NestedScrollViewCustomScrollView extends CustomScrollView { @required ScrollController controller, @required List slivers, @required this.handle, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : super( scrollDirection: scrollDirection, reverse: reverse, physics: physics, controller: controller, slivers: slivers, + dragStartBehavior: dragStartBehavior, ); final SliverOverlapAbsorberHandle handle; diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart index 371cd58b8d..a46f22e4b7 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -7,6 +7,7 @@ import 'dart:math' as math; import 'package:flutter/physics.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'basic.dart'; import 'debug.dart'; @@ -423,6 +424,7 @@ class PageView extends StatefulWidget { this.pageSnapping = true, this.onPageChanged, List children = const [], + this.dragStartBehavior = DragStartBehavior.start, }) : controller = controller ?? _defaultPageController, childrenDelegate = SliverChildListDelegate(children), super(key: key); @@ -449,6 +451,7 @@ class PageView extends StatefulWidget { this.onPageChanged, @required IndexedWidgetBuilder itemBuilder, int itemCount, + this.dragStartBehavior = DragStartBehavior.start, }) : controller = controller ?? _defaultPageController, childrenDelegate = SliverChildBuilderDelegate(itemBuilder, childCount: itemCount), super(key: key); @@ -464,6 +467,7 @@ class PageView extends StatefulWidget { this.pageSnapping = true, this.onPageChanged, @required this.childrenDelegate, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(childrenDelegate != null), controller = controller ?? _defaultPageController, super(key: key); @@ -516,6 +520,9 @@ class PageView extends StatefulWidget { /// respectively. final SliverChildDelegate childrenDelegate; + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + @override _PageViewState createState() => _PageViewState(); } @@ -562,6 +569,7 @@ class _PageViewState extends State { return false; }, child: Scrollable( + dragStartBehavior: widget.dragStartBehavior, axisDirection: axisDirection, controller: widget.controller, physics: physics, diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart index f4575e629d..7e701a5448 100644 --- a/packages/flutter/lib/src/widgets/scroll_view.dart +++ b/packages/flutter/lib/src/widgets/scroll_view.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'package:flutter/rendering.dart'; +import 'package:flutter/gestures.dart'; import 'basic.dart'; import 'framework.dart'; @@ -60,8 +61,10 @@ abstract class ScrollView extends StatelessWidget { this.shrinkWrap = false, this.cacheExtent, this.semanticChildCount, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(reverse != null), assert(shrinkWrap != null), + assert(dragStartBehavior != null), assert(!(controller != null && primary == true), 'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. ' 'You cannot both set primary to true and pass an explicit controller.' @@ -187,6 +190,9 @@ abstract class ScrollView extends StatelessWidget { /// * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property. final int semanticChildCount; + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + /// Returns the [AxisDirection] in which the scroll view scrolls. /// /// Combines the [scrollDirection] with the [reverse] boolean to obtain the @@ -246,6 +252,7 @@ abstract class ScrollView extends StatelessWidget { ? PrimaryScrollController.of(context) : controller; final Scrollable scrollable = Scrollable( + dragStartBehavior: dragStartBehavior, axisDirection: axisDirection, controller: scrollController, physics: physics, @@ -397,6 +404,7 @@ class CustomScrollView extends ScrollView { double cacheExtent, this.slivers = const [], int semanticChildCount, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : super( key: key, scrollDirection: scrollDirection, @@ -407,6 +415,7 @@ class CustomScrollView extends ScrollView { shrinkWrap: shrinkWrap, cacheExtent: cacheExtent, semanticChildCount: semanticChildCount, + dragStartBehavior: dragStartBehavior, ); /// The slivers to place inside the viewport. @@ -439,6 +448,7 @@ abstract class BoxScrollView extends ScrollView { this.padding, double cacheExtent, int semanticChildCount, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : super( key: key, scrollDirection: scrollDirection, @@ -449,6 +459,7 @@ abstract class BoxScrollView extends ScrollView { shrinkWrap: shrinkWrap, cacheExtent: cacheExtent, semanticChildCount: semanticChildCount, + dragStartBehavior: dragStartBehavior, ); /// The amount of space by which to inset the children. @@ -739,6 +750,7 @@ class ListView extends BoxScrollView { double cacheExtent, List children = const [], int semanticChildCount, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : childrenDelegate = SliverChildListDelegate( children, addAutomaticKeepAlives: addAutomaticKeepAlives, @@ -755,6 +767,7 @@ class ListView extends BoxScrollView { padding: padding, cacheExtent: cacheExtent, semanticChildCount: semanticChildCount ?? children.length, + dragStartBehavior: dragStartBehavior, ); /// Creates a scrollable, linear array of widgets that are created on demand. @@ -800,6 +813,7 @@ class ListView extends BoxScrollView { bool addSemanticIndexes = true, double cacheExtent, int semanticChildCount, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : childrenDelegate = SliverChildBuilderDelegate( itemBuilder, childCount: itemCount, @@ -817,6 +831,7 @@ class ListView extends BoxScrollView { padding: padding, cacheExtent: cacheExtent, semanticChildCount: semanticChildCount ?? itemCount, + dragStartBehavior: dragStartBehavior, ); /// Creates a fixed-length scrollable linear array of list "items" separated @@ -1250,6 +1265,7 @@ class GridView extends BoxScrollView { @required this.childrenDelegate, double cacheExtent, int semanticChildCount, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : assert(gridDelegate != null), assert(childrenDelegate != null), super( @@ -1263,6 +1279,7 @@ class GridView extends BoxScrollView { padding: padding, cacheExtent: cacheExtent, semanticChildCount: semanticChildCount, + dragStartBehavior: dragStartBehavior, ); /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in @@ -1298,6 +1315,7 @@ class GridView extends BoxScrollView { double cacheExtent, List children = const [], int semanticChildCount, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, mainAxisSpacing: mainAxisSpacing, @@ -1320,6 +1338,7 @@ class GridView extends BoxScrollView { padding: padding, cacheExtent: cacheExtent, semanticChildCount: semanticChildCount ?? children.length, + dragStartBehavior: dragStartBehavior, ); /// Creates a scrollable, 2D array of widgets with tiles that each have a @@ -1354,6 +1373,7 @@ class GridView extends BoxScrollView { bool addSemanticIndexes = true, List children = const [], int semanticChildCount, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: maxCrossAxisExtent, mainAxisSpacing: mainAxisSpacing, @@ -1375,6 +1395,7 @@ class GridView extends BoxScrollView { shrinkWrap: shrinkWrap, padding: padding, semanticChildCount: semanticChildCount ?? children.length, + dragStartBehavior: dragStartBehavior, ); /// A delegate that controls the layout of the children within the [GridView]. diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index d6768c05a5..42b91a9747 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -81,7 +81,9 @@ class Scrollable extends StatefulWidget { @required this.viewportBuilder, this.excludeFromSemantics = false, this.semanticChildCount, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(axisDirection != null), + assert(dragStartBehavior != null), assert(viewportBuilder != null), assert(excludeFromSemantics != null), super (key: key); @@ -180,6 +182,25 @@ class Scrollable extends StatefulWidget { /// * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property. final int semanticChildCount; + /// {@template flutter.widgets.scrollable.dragStartBehavior} + /// 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. + /// + /// 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]. + /// + /// See also: + /// + /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. + /// {@endtemplate} + final DragStartBehavior dragStartBehavior; + /// The axis along which the scroll view scrolls. /// /// Determined by the [axisDirection]. @@ -391,7 +412,8 @@ class ScrollableState extends State with TickerProviderStateMixin ..onCancel = _handleDragCancel ..minFlingDistance = _physics?.minFlingDistance ..minFlingVelocity = _physics?.minFlingVelocity - ..maxFlingVelocity = _physics?.maxFlingVelocity; + ..maxFlingVelocity = _physics?.maxFlingVelocity + ..dragStartBehavior = widget.dragStartBehavior; }, ), }; @@ -409,7 +431,8 @@ class ScrollableState extends State with TickerProviderStateMixin ..onCancel = _handleDragCancel ..minFlingDistance = _physics?.minFlingDistance ..minFlingVelocity = _physics?.minFlingVelocity - ..maxFlingVelocity = _physics?.maxFlingVelocity; + ..maxFlingVelocity = _physics?.maxFlingVelocity + ..dragStartBehavior = widget.dragStartBehavior; }, ), }; 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 2520283ec0..dbd039928a 100644 --- a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'package:flutter/rendering.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'basic.dart'; import 'framework.dart'; @@ -192,7 +193,9 @@ class SingleChildScrollView extends StatelessWidget { this.physics, this.controller, this.child, + this.dragStartBehavior = DragStartBehavior.start, }) : assert(scrollDirection != null), + assert(dragStartBehavior != null), assert(!(controller != null && primary == true), 'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. ' 'You cannot both set primary to true and pass an explicit controller.' @@ -259,6 +262,9 @@ class SingleChildScrollView extends StatelessWidget { /// {@macro flutter.widgets.child} final Widget child; + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + AxisDirection _getDirection(BuildContext context) { return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse); } @@ -273,6 +279,7 @@ class SingleChildScrollView extends StatelessWidget { ? PrimaryScrollController.of(context) : controller; final Scrollable scrollable = Scrollable( + dragStartBehavior: dragStartBehavior, axisDirection: axisDirection, controller: scrollController, physics: physics, diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 7af804fda1..5d8110e927 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -8,6 +8,7 @@ import 'package:flutter/gestures.dart' show kDoubleTapTimeout, kDoubleTapSlop; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/scheduler.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'basic.dart'; import 'container.dart'; @@ -229,6 +230,7 @@ class TextSelectionOverlay { @required this.renderObject, this.selectionControls, this.selectionDelegate, + this.dragStartBehavior = DragStartBehavior.start, }): assert(value != null), assert(context != null), _value = value { @@ -263,6 +265,23 @@ class TextSelectionOverlay { /// text field. final TextSelectionDelegate selectionDelegate; + /// 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. + /// + /// 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]. + /// + /// See also: + /// + /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. + final DragStartBehavior dragStartBehavior; + /// Controls the fade-in animations. static const Duration _fadeDuration = Duration(milliseconds: 150); AnimationController _handleController; @@ -365,9 +384,8 @@ class TextSelectionOverlay { Widget _buildHandle(BuildContext context, _TextSelectionHandlePosition position) { if ((_selection.isCollapsed && position == _TextSelectionHandlePosition.end) || - selectionControls == null) + selectionControls == null) return Container(); // hide the second handle when collapsed - return FadeTransition( opacity: _handleOpacity, child: _TextSelectionHandleOverlay( @@ -378,6 +396,7 @@ class TextSelectionOverlay { selection: _selection, selectionControls: selectionControls, position: position, + dragStartBehavior: dragStartBehavior, ) ); } @@ -447,7 +466,8 @@ class _TextSelectionHandleOverlay extends StatefulWidget { @required this.renderObject, @required this.onSelectionHandleChanged, @required this.onSelectionHandleTapped, - @required this.selectionControls + @required this.selectionControls, + this.dragStartBehavior = DragStartBehavior.start, }) : super(key: key); final TextSelection selection; @@ -457,6 +477,7 @@ class _TextSelectionHandleOverlay extends StatefulWidget { final ValueChanged onSelectionHandleChanged; final VoidCallback onSelectionHandleTapped; final TextSelectionControls selectionControls; + final DragStartBehavior dragStartBehavior; @override _TextSelectionHandleOverlayState createState() => _TextSelectionHandleOverlayState(); @@ -528,6 +549,7 @@ class _TextSelectionHandleOverlayState extends State<_TextSelectionHandleOverlay link: widget.layerLink, showWhenUnlinked: false, child: GestureDetector( + dragStartBehavior: widget.dragStartBehavior, onPanStart: _handleDragStart, onPanUpdate: _handleDragUpdate, onTap: _handleTap, diff --git a/packages/flutter/test/cupertino/switch_test.dart b/packages/flutter/test/cupertino/switch_test.dart index c08ea4ab49..08f4a787d6 100644 --- a/packages/flutter/test/cupertino/switch_test.dart +++ b/packages/flutter/test/cupertino/switch_test.dart @@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/gestures.dart'; void main() { testWidgets('Switch can toggle on tap', (WidgetTester tester) async { @@ -18,6 +19,7 @@ void main() { child: CupertinoSwitch( key: switchKey, value: value, + dragStartBehavior: DragStartBehavior.down, onChanged: (bool newValue) { setState(() { value = newValue; @@ -46,6 +48,7 @@ void main() { return Center( child: CupertinoSwitch( value: value, + dragStartBehavior: DragStartBehavior.down, onChanged: (bool newValue) { setState(() { value = newValue; @@ -79,6 +82,88 @@ void main() { expect(value, isFalse); }); + testWidgets('Switch can drag with dragStartBehavior', (WidgetTester tester) async { + bool value = false; + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Center( + child: CupertinoSwitch( + value: value, + dragStartBehavior: DragStartBehavior.down, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), + ); + }, + ), + ), + ); + + expect(value, isFalse); + await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0)); + expect(value, isFalse); + + await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0)); + expect(value, isTrue); + await tester.pump(); + await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0)); + expect(value, isTrue); + await tester.pump(); + await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0)); + expect(value, isFalse); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Center( + child: CupertinoSwitch( + value: value, + dragStartBehavior: DragStartBehavior.start, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + final Rect switchRect = tester.getRect(find.byType(CupertinoSwitch)); + + TestGesture gesture = await tester.startGesture(switchRect.center); + // We have to execute the drag in two frames because the first update will + // just set the start position. + await gesture.moveBy(const Offset(20.0, 0.0)); + await gesture.moveBy(const Offset(20.0, 0.0)); + expect(value, isTrue); + await gesture.up(); + await tester.pump(); + + gesture = await tester.startGesture(switchRect.center); + await gesture.moveBy(const Offset(20.0, 0.0)); + await gesture.moveBy(const Offset(20.0, 0.0)); + expect(value, isTrue); + await gesture.up(); + await tester.pump(); + + gesture = await tester.startGesture(switchRect.center); + await gesture.moveBy(const Offset(-20.0, 0.0)); + await gesture.moveBy(const Offset(-20.0, 0.0)); + expect(value, isFalse); + }); + testWidgets('Switch can drag (RTL)', (WidgetTester tester) async { bool value = false; @@ -90,6 +175,7 @@ void main() { return Center( child: CupertinoSwitch( value: value, + dragStartBehavior: DragStartBehavior.down, onChanged: (bool newValue) { setState(() { value = newValue; diff --git a/packages/flutter/test/gestures/drag_test.dart b/packages/flutter/test/gestures/drag_test.dart index 4d9946669b..8349c0bcd7 100644 --- a/packages/flutter/test/gestures/drag_test.dart +++ b/packages/flutter/test/gestures/drag_test.dart @@ -58,8 +58,7 @@ 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; - expect(updatedScrollDelta, const Offset(10.0, 20.0)); - updatedScrollDelta = null; + expect(updatedScrollDelta, null); expect(didEndPan, isFalse); expect(didTap, isFalse); @@ -82,7 +81,7 @@ void main() { }); testGesture('Should recognize drag', (GestureTester tester) { - final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); + final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; bool didStartDrag = false; drag.onStart = (_) { @@ -135,7 +134,7 @@ void main() { }); testGesture('Should report original timestamps', (GestureTester tester) { - final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); + final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; Duration startTimestamp; drag.onStart = (DragStartDetails details) { @@ -165,9 +164,98 @@ void main() { drag.dispose(); }); - testGesture('Drag with multiple pointers', (GestureTester tester) { - final HorizontalDragGestureRecognizer drag1 = HorizontalDragGestureRecognizer(); - final VerticalDragGestureRecognizer drag2 = VerticalDragGestureRecognizer(); + 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 report initial down point to onStart with a down configuration', (GestureTester tester) { + final HorizontalDragGestureRecognizer drag = + HorizontalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; + final VerticalDragGestureRecognizer competingDrag = VerticalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; + + Offset positionAtOnStart; + drag.onStart = (DragStartDetails details) { + positionAtOnStart = details.globalPosition; + }; + Offset updateOffset; + Offset updateDelta; + drag.onUpdate = (DragUpdateDetails details) { + updateOffset = details.globalPosition; + updateDelta = details.delta; + }; + + 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(10.0, 10.0)); + + // The drag is horizontal so we're going to ignore the vertical delta position + // when calculating the new global position. + expect(updateOffset, const Offset(30.0, 10.0)); + expect(updateDelta, const Offset(20.0, 0.0)); + }); + + testGesture('Drag with multiple pointers in down behavior', (GestureTester tester) { + final HorizontalDragGestureRecognizer drag1 = + HorizontalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; + final VerticalDragGestureRecognizer drag2 = + VerticalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; final List log = []; drag1.onDown = (_) { log.add('drag1-down'); }; @@ -235,7 +323,7 @@ void main() { }); testGesture('Clamp max velocity', (GestureTester tester) { - final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); + final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; Velocity velocity; double primaryVelocity; @@ -269,7 +357,7 @@ void main() { }); testGesture('Synthesized pointer events are ignored for velocity tracking', (GestureTester tester) { - final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); + final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; Velocity velocity; drag.onEnd = (DragEndDetails details) { @@ -303,7 +391,7 @@ void main() { /// Checks that quick flick gestures with 1 down, 2 move and 1 up pointer /// events still have a velocity testGesture('Quick flicks have velocity', (GestureTester tester) { - final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); + final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; Velocity velocity; drag.onEnd = (DragEndDetails details) { @@ -333,16 +421,18 @@ void main() { }); testGesture('Should recognize drag', (GestureTester tester) { - final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); + final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; bool didStartDrag = false; drag.onStart = (_) { didStartDrag = true; }; - double updatedDelta; + Offset updateDelta; + double updatePrimaryDelta; drag.onUpdate = (DragUpdateDetails details) { - updatedDelta = details.primaryDelta; + updateDelta = details.delta; + updatePrimaryDelta = details.primaryDelta; }; bool didEndDrag = false; @@ -354,31 +444,39 @@ void main() { final PointerDownEvent down = pointer.down(const Offset(10.0, 10.0)); drag.addPointer(down); tester.closeArena(5); + expect(didStartDrag, isFalse); - expect(updatedDelta, isNull); + expect(updateDelta, isNull); + expect(updatePrimaryDelta, isNull); expect(didEndDrag, isFalse); tester.route(down); expect(didStartDrag, isTrue); - expect(updatedDelta, isNull); + expect(updateDelta, isNull); + expect(updatePrimaryDelta, isNull); expect(didEndDrag, isFalse); - - tester.route(pointer.move(const Offset(20.0, 25.0))); - expect(didStartDrag, isTrue); didStartDrag = false; - expect(updatedDelta, 10.0); - updatedDelta = null; - expect(didEndDrag, isFalse); tester.route(pointer.move(const Offset(20.0, 25.0))); expect(didStartDrag, isFalse); - expect(updatedDelta, 0.0); - updatedDelta = null; + expect(updateDelta, const Offset(10.0, 0.0)); + expect(updatePrimaryDelta, 10.0); expect(didEndDrag, isFalse); + updateDelta = null; + updatePrimaryDelta = null; + + tester.route(pointer.move(const Offset(20.0, 25.0))); + expect(didStartDrag, isFalse); + expect(updateDelta, const Offset(0.0, 0.0)); + expect(updatePrimaryDelta, 0.0); + expect(didEndDrag, isFalse); + updateDelta = null; + updatePrimaryDelta = null; tester.route(pointer.up()); expect(didStartDrag, isFalse); - expect(updatedDelta, isNull); + expect(updateDelta, isNull); + expect(updatePrimaryDelta, isNull); expect(didEndDrag, isTrue); didEndDrag = false; @@ -386,21 +484,36 @@ void main() { }); testGesture('Should recognize drag', (GestureTester tester) { - final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); + final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down; - Offset newGlobalPosition; + Offset latestGlobalPosition; + drag.onStart = (DragStartDetails details) { + latestGlobalPosition = details.globalPosition; + }; + Offset latestDelta; drag.onUpdate = (DragUpdateDetails details) { - newGlobalPosition = details.globalPosition; + latestGlobalPosition = details.globalPosition; + latestDelta = details.delta; }; final TestPointer pointer = TestPointer(5); final PointerDownEvent down = pointer.down(const Offset(10.0, 10.0)); drag.addPointer(down); - tester.route(pointer.move(const Offset(20.0, 25.0))); tester.closeArena(5); + tester.route(down); - expect(newGlobalPosition, const Offset(20.0, 10.0)); + expect(latestGlobalPosition, const Offset(10.0, 10.0)); + expect(latestDelta, isNull); + + tester.route(pointer.move(const Offset(20.0, 25.0))); + expect(latestGlobalPosition, const Offset(20.0, 25.0)); + expect(latestDelta, const Offset(10.0, 0.0)); + + tester.route(pointer.move(const Offset(0.0, 45.0))); + expect(latestGlobalPosition, const Offset(0.0, 45.0)); + expect(latestDelta, const Offset(-20.0, 0.0)); + tester.route(pointer.up()); drag.dispose(); }); -} +} \ No newline at end of file diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart index ca5f19bf87..a5fa10528e 100644 --- a/packages/flutter/test/material/date_picker_test.dart +++ b/packages/flutter/test/material/date_picker_test.dart @@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; @@ -46,8 +47,10 @@ void _tests() { return Container( width: 400.0, child: SingleChildScrollView( + dragStartBehavior: DragStartBehavior.down, child: Material( child: MonthPicker( + dragStartBehavior: DragStartBehavior.down, firstDate: DateTime(0), lastDate: DateTime(9999), key: _datePickerKey, @@ -63,7 +66,7 @@ void _tests() { ); }, ), - ) + ), ); expect(_selectedDate, equals(DateTime(2016, DateTime.july, 26))); diff --git a/packages/flutter/test/material/ink_well_test.dart b/packages/flutter/test/material/ink_well_test.dart index 0ae9410327..e89d1baf1f 100644 --- a/packages/flutter/test/material/ink_well_test.dart +++ b/packages/flutter/test/material/ink_well_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/gestures.dart'; import '../rendering/mock_canvas.dart'; import '../widgets/semantics_tester.dart'; @@ -164,6 +165,7 @@ void main() { link: LayerLink(), child: ListView( addAutomaticKeepAlives: keepAlive, + dragStartBehavior: DragStartBehavior.down, children: [ Container(height: 500.0, child: InkWell(onTap: () { }, child: const Placeholder())), Container(height: 500.0), diff --git a/packages/flutter/test/material/paginated_data_table_test.dart b/packages/flutter/test/material/paginated_data_table_test.dart index 9221a6be44..c1d833e20e 100644 --- a/packages/flutter/test/material/paginated_data_table_test.dart +++ b/packages/flutter/test/material/paginated_data_table_test.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'data_table_test_utils.dart'; @@ -248,26 +249,29 @@ void main() { testWidgets('PaginatedDataTable footer scrolls', (WidgetTester tester) async { final TestDataSource source = TestDataSource(); - await tester.pumpWidget(MaterialApp( - home: Align( - alignment: Alignment.topLeft, - child: SizedBox( - width: 100.0, - child: PaginatedDataTable( - header: const Text('HEADER'), - source: source, - rowsPerPage: 5, - availableRowsPerPage: const [ 5 ], - onRowsPerPageChanged: (int rowsPerPage) { }, - columns: const [ - DataColumn(label: Text('COL1')), - DataColumn(label: Text('COL2')), - DataColumn(label: Text('COL3')), - ], + await tester.pumpWidget( + MaterialApp( + home: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 100.0, + child: PaginatedDataTable( + header: const Text('HEADER'), + source: source, + rowsPerPage: 5, + dragStartBehavior: DragStartBehavior.down, + availableRowsPerPage: const [ 5 ], + onRowsPerPageChanged: (int rowsPerPage) { }, + columns: const [ + DataColumn(label: Text('COL1')), + DataColumn(label: Text('COL2')), + DataColumn(label: Text('COL3')), + ], + ), ), ), ), - )); + ); expect(find.text('Rows per page:'), findsOneWidget); expect(tester.getTopLeft(find.text('Rows per page:')).dx, lessThan(0.0)); // off screen await tester.dragFrom( diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart index f8d87d3840..d1046014f9 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import '../widgets/semantics_tester.dart'; @@ -201,6 +202,7 @@ void main() { drawer: Drawer( key: drawerKey, child: ListView( + dragStartBehavior: DragStartBehavior.down, controller: scrollOffset, children: List.generate(10, (int index) => SizedBox(height: 100.0, child: Text('D$index')) @@ -630,6 +632,7 @@ void main() { viewInsets: EdgeInsets.only(bottom: 200.0), ), child: Scaffold( + drawerDragStartBehavior: DragStartBehavior.down, appBar: PreferredSize( preferredSize: const Size(11.0, 13.0), child: Container( diff --git a/packages/flutter/test/material/switch_test.dart b/packages/flutter/test/material/switch_test.dart index 59a7c5f827..84b8bebc04 100644 --- a/packages/flutter/test/material/switch_test.dart +++ b/packages/flutter/test/material/switch_test.dart @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/gestures.dart'; import '../rendering/mock_canvas.dart'; import '../widgets/semantics_tester.dart'; @@ -25,6 +25,7 @@ void main() { return Material( child: Center( child: Switch( + dragStartBehavior: DragStartBehavior.down, key: switchKey, value: value, onChanged: (bool newValue) { @@ -54,6 +55,7 @@ void main() { child: Material( child: Center( child: Switch( + dragStartBehavior: DragStartBehavior.down, value: true, onChanged: (bool newValue) {}, ), @@ -73,6 +75,7 @@ void main() { child: Material( child: Center( child: Switch( + dragStartBehavior: DragStartBehavior.down, value: true, onChanged: (bool newValue) {}, ), @@ -96,6 +99,7 @@ void main() { return Material( child: Center( child: Switch( + dragStartBehavior: DragStartBehavior.down, value: value, onChanged: (bool newValue) { setState(() { @@ -131,6 +135,93 @@ void main() { expect(value, isFalse); }); + testWidgets('Switch can drag with dragStartBehavior', (WidgetTester tester) async { + bool value = false; + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: Center( + child: Switch( + dragStartBehavior: DragStartBehavior.down, + value: value, + onChanged: (bool newValue) { + setState(() { + value = newValue; + } + ); + } + ), + ), + ); + }, + ), + ), + ); + + expect(value, isFalse); + await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0)); + expect(value, isFalse); + + await tester.drag(find.byType(Switch), const Offset(30.0, 0.0)); + expect(value, isTrue); + await tester.pump(); + await tester.drag(find.byType(Switch), const Offset(30.0, 0.0)); + expect(value, isTrue); + await tester.pump(); + await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0)); + expect(value, isFalse); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: Center( + child: Switch( + dragStartBehavior: DragStartBehavior.start, + value: value, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + } + ), + ), + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + final Rect switchRect = tester.getRect(find.byType(Switch)); + + TestGesture gesture = await tester.startGesture(switchRect.center); + // We have to execute the drag in two frames because the first update will + // just set the start position. + await gesture.moveBy(const Offset(20.0, 0.0)); + await gesture.moveBy(const Offset(20.0, 0.0)); + expect(value, isTrue); + await gesture.up(); + await tester.pump(); + + gesture = await tester.startGesture(switchRect.center); + await gesture.moveBy(const Offset(20.0, 0.0)); + await gesture.moveBy(const Offset(20.0, 0.0)); + expect(value, isTrue); + await gesture.up(); + await tester.pump(); + + gesture = await tester.startGesture(switchRect.center); + await gesture.moveBy(const Offset(-20.0, 0.0)); + await gesture.moveBy(const Offset(-20.0, 0.0)); + expect(value, isFalse); + }); + testWidgets('Switch can drag (RTL)', (WidgetTester tester) async { bool value = false; @@ -142,6 +233,7 @@ void main() { return Material( child: Center( child: Switch( + dragStartBehavior: DragStartBehavior.down, value: value, onChanged: (bool newValue) { setState(() { @@ -185,6 +277,7 @@ void main() { return Material( child: Center( child: Switch( + dragStartBehavior: DragStartBehavior.down, value: value, onChanged: (bool newValue) { setState(() { @@ -306,6 +399,7 @@ void main() { return Material( child: Center( child: Switch( + dragStartBehavior: DragStartBehavior.down, value: value, onChanged: (bool newValue) { setState(() { @@ -365,6 +459,7 @@ void main() { return Material( child: Center( child: Switch( + dragStartBehavior: DragStartBehavior.down, value: value, onChanged: (bool newValue) { setState(() { diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 3e1327b326..59b90ca999 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -12,6 +12,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; @@ -501,9 +502,12 @@ void main() { final TextEditingController controller = TextEditingController(); await tester.pumpWidget( - overlay( - child: TextField( - controller: controller, + MaterialApp( + home: Material( + child: TextField( + dragStartBehavior: DragStartBehavior.down, + controller: controller, + ), ), ), ); @@ -542,7 +546,7 @@ void main() { await tester.pump(); expect(controller.selection.baseOffset, selection.baseOffset); - expect(controller.selection.extentOffset, selection.extentOffset+2); + expect(controller.selection.extentOffset, selection.extentOffset); // Drag the left handle 2 letters to the left. handlePos = endpoints[0].point + const Offset(-1.0, 1.0); @@ -554,8 +558,8 @@ void main() { await gesture.up(); await tester.pump(); - expect(controller.selection.baseOffset, selection.baseOffset-2); - expect(controller.selection.extentOffset, selection.extentOffset+2); + expect(controller.selection.baseOffset, selection.baseOffset); + expect(controller.selection.extentOffset, selection.extentOffset); }); testWidgets('Can use selection toolbar', (WidgetTester tester) async { @@ -826,6 +830,7 @@ void main() { await tester.pumpWidget( overlay( child: TextField( + dragStartBehavior: DragStartBehavior.down, controller: controller, style: const TextStyle(color: Colors.black, fontSize: 34.0), maxLines: 3, @@ -909,6 +914,7 @@ void main() { await tester.pumpWidget( overlay( child: TextField( + dragStartBehavior: DragStartBehavior.down, key: textFieldKey, controller: controller, style: const TextStyle(color: Colors.black, fontSize: 34.0), diff --git a/packages/flutter/test/widgets/automatic_keep_alive_test.dart b/packages/flutter/test/widgets/automatic_keep_alive_test.dart index 404e633992..68bfe86466 100644 --- a/packages/flutter/test/widgets/automatic_keep_alive_test.dart +++ b/packages/flutter/test/widgets/automatic_keep_alive_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; class Leaf extends StatefulWidget { const Leaf({ Key key, this.child }) : super(key: key); @@ -476,6 +477,7 @@ void main() { await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ListView.builder( + dragStartBehavior: DragStartBehavior.down, addSemanticIndexes: false, itemCount: 50, itemBuilder: (BuildContext context, int index){ diff --git a/packages/flutter/test/widgets/dismissible_test.dart b/packages/flutter/test/widgets/dismissible_test.dart index 420fdcc0ea..9623121b93 100644 --- a/packages/flutter/test/widgets/dismissible_test.dart +++ b/packages/flutter/test/widgets/dismissible_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; const double itemExtent = 100.0; Axis scrollDirection = Axis.vertical; @@ -21,6 +22,7 @@ Widget buildTest({ double startToEndThreshold, TextDirection textDirection = Tex builder: (BuildContext context, StateSetter setState) { Widget buildDismissibleItem(int item) { return Dismissible( + dragStartBehavior: DragStartBehavior.down, key: ValueKey(item), direction: dismissDirection, onDismissed: (DismissDirection direction) { @@ -49,6 +51,7 @@ Widget buildTest({ double startToEndThreshold, TextDirection textDirection = Tex return Container( padding: const EdgeInsets.all(10.0), child: ListView( + dragStartBehavior: DragStartBehavior.down, scrollDirection: scrollDirection, itemExtent: itemExtent, children: [0, 1, 2, 3, 4] @@ -199,6 +202,7 @@ class Test1215DismissibleWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Dismissible( + dragStartBehavior: DragStartBehavior.down, key: ObjectKey(text), child: AspectRatio( aspectRatio: 1.0, diff --git a/packages/flutter/test/widgets/draggable_test.dart b/packages/flutter/test/widgets/draggable_test.dart index 97941fce20..ef8e964919 100644 --- a/packages/flutter/test/widgets/draggable_test.dart +++ b/packages/flutter/test/widgets/draggable_test.dart @@ -383,6 +383,7 @@ void main() { await tester.pumpWidget(MaterialApp( home: ListView( + dragStartBehavior: DragStartBehavior.down, children: [ DragTarget( builder: (BuildContext context, List data, List rejects) { @@ -489,6 +490,7 @@ void main() { await tester.pumpWidget(MaterialApp( home: ListView( + dragStartBehavior: DragStartBehavior.down, scrollDirection: Axis.horizontal, children: [ DragTarget( diff --git a/packages/flutter/test/widgets/drawer_test.dart b/packages/flutter/test/widgets/drawer_test.dart index 30868b09ba..5dc9a7f6f2 100644 --- a/packages/flutter/test/widgets/drawer_test.dart +++ b/packages/flutter/test/widgets/drawer_test.dart @@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'semantics_tester.dart'; @@ -82,6 +83,7 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( + drawerDragStartBehavior: DragStartBehavior.down, key: scaffoldKey, drawer: Drawer( child: ListView( @@ -134,6 +136,7 @@ void main() { home: Directionality( textDirection: TextDirection.rtl, child: Scaffold( + drawerDragStartBehavior: DragStartBehavior.down, key: scaffoldKey, drawer: Drawer( child: ListView( diff --git a/packages/flutter/test/widgets/gesture_detector_test.dart b/packages/flutter/test/widgets/gesture_detector_test.dart index f7acb45d5d..922126bf9a 100644 --- a/packages/flutter/test/widgets/gesture_detector_test.dart +++ b/packages/flutter/test/widgets/gesture_detector_test.dart @@ -64,6 +64,7 @@ void main() { const Offset upLocation = Offset(10.0, 50.0); // must be far enough to be more than kTouchSlop final Widget widget = GestureDetector( + dragStartBehavior: DragStartBehavior.down, onVerticalDragUpdate: (DragUpdateDetails details) { dragDistance += details.primaryDelta; }, onVerticalDragEnd: (DragEndDetails details) { gestureCount += 1; }, onHorizontalDragUpdate: (DragUpdateDetails details) { fail('gesture should not match'); }, diff --git a/packages/flutter/test/widgets/grid_view_test.dart b/packages/flutter/test/widgets/grid_view_test.dart index 38b478630f..3938ac14d4 100644 --- a/packages/flutter/test/widgets/grid_view_test.dart +++ b/packages/flutter/test/widgets/grid_view_test.dart @@ -4,6 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import '../rendering/mock_canvas.dart'; import 'states.dart'; @@ -14,6 +15,7 @@ void main() { Directionality( textDirection: TextDirection.ltr, child: GridView.count( + dragStartBehavior: DragStartBehavior.down, crossAxisCount: 4, children: const [], ), @@ -28,9 +30,11 @@ void main() { Directionality( textDirection: TextDirection.ltr, child: GridView.count( + dragStartBehavior: DragStartBehavior.down, crossAxisCount: 4, children: kStates.map((String state) { return GestureDetector( + dragStartBehavior: DragStartBehavior.down, onTap: () { log.add(state); }, @@ -99,9 +103,11 @@ void main() { Directionality( textDirection: TextDirection.ltr, child: GridView.extent( + dragStartBehavior: DragStartBehavior.down, maxCrossAxisExtent: 200.0, children: kStates.map((String state) { return GestureDetector( + dragStartBehavior: DragStartBehavior.down, onTap: () { log.add(state); }, diff --git a/packages/flutter/test/widgets/heroes_test.dart b/packages/flutter/test/widgets/heroes_test.dart index e33291b4bf..23e6460460 100644 --- a/packages/flutter/test/widgets/heroes_test.dart +++ b/packages/flutter/test/widgets/heroes_test.dart @@ -1373,8 +1373,11 @@ void main() { expect(find.byKey(secondKey), isOnstage); expect(find.byKey(secondKey), isInCard); - final TestGesture gesture = await tester.startGesture(const Offset(5.0, 200.0)); - await gesture.moveBy(const Offset(200.0, 0.0)); + final TestGesture gesture = await tester.startGesture(const Offset(5.0, 200.0)); + await gesture.moveBy(const Offset(20.0, 0.0)); + await gesture.moveBy(const Offset(180.0, 0.0)); + await gesture.up(); + await tester.pump(); await tester.pump(); diff --git a/packages/flutter/test/widgets/listview_end_append_test.dart b/packages/flutter/test/widgets/listview_end_append_test.dart index 0f9f1f4bee..e09969e9c2 100644 --- a/packages/flutter/test/widgets/listview_end_append_test.dart +++ b/packages/flutter/test/widgets/listview_end_append_test.dart @@ -4,6 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; void main() { testWidgets('ListView.builder() fixed itemExtent, scroll to end, append, scroll', (WidgetTester tester) async { @@ -13,6 +14,7 @@ void main() { return Directionality( textDirection: TextDirection.ltr, child: ListView.builder( + dragStartBehavior: DragStartBehavior.down, itemExtent: 200.0, itemCount: itemCount, itemBuilder: (BuildContext context, int index) => Text('item $index'), @@ -40,6 +42,7 @@ void main() { return Directionality( textDirection: TextDirection.ltr, child: ListView.builder( + dragStartBehavior: DragStartBehavior.down, itemCount: itemCount, itemBuilder: (BuildContext context, int index) { return SizedBox( diff --git a/packages/flutter/test/widgets/nested_scroll_view_test.dart b/packages/flutter/test/widgets/nested_scroll_view_test.dart index b52360bc2a..354126d9a7 100644 --- a/packages/flutter/test/widgets/nested_scroll_view_test.dart +++ b/packages/flutter/test/widgets/nested_scroll_view_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import '../rendering/mock_canvas.dart'; @@ -34,9 +35,11 @@ Widget buildTest({ ScrollController controller, String title ='TTTTTTTT' }) { child: MediaQuery( data: const MediaQueryData(), child: Scaffold( + drawerDragStartBehavior: DragStartBehavior.down, body: DefaultTabController( length: 4, child: NestedScrollView( + dragStartBehavior: DragStartBehavior.down, controller: controller, headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return [ @@ -79,6 +82,7 @@ Widget buildTest({ ScrollController controller, String title ='TTTTTTTT' }) { ], ), ListView( + dragStartBehavior: DragStartBehavior.down, children: [ Container( height: 100.0, @@ -90,6 +94,7 @@ Widget buildTest({ ScrollController controller, String title ='TTTTTTTT' }) { child: const Center(child: Text('ccc1')), ), ListView( + dragStartBehavior: DragStartBehavior.down, children: [ Container( height: 10000.0, @@ -361,6 +366,7 @@ void main() { DefaultTabController( length: _tabs.length, // This is the number of tabs. child: NestedScrollView( + dragStartBehavior: DragStartBehavior.down, headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { buildCount += 1; // THIS LINE IS NOT IN THE ORIGINAL -- ADDED FOR TEST // These are the slivers that show up in the "outer" scroll view. @@ -390,12 +396,14 @@ void main() { bottom: TabBar( // These are the widgets to put in each tab in the tab bar. tabs: _tabs.map((String name) => Tab(text: name)).toList(), + dragStartBehavior: DragStartBehavior.down, ), ), ), ]; }, body: TabBarView( + dragStartBehavior: DragStartBehavior.down, // These are the contents of the tab views, below the tabs. children: _tabs.map((String name) { return SafeArea( @@ -416,6 +424,7 @@ void main() { // it allows the list to remember its scroll position when // the tab view is not on the screen. key: PageStorageKey(name), + dragStartBehavior: DragStartBehavior.down, slivers: [ SliverOverlapInjector( // This is the flip side of the SliverOverlapAbsorber above. @@ -590,6 +599,7 @@ void main() { child: DefaultTabController( length: 1, child: NestedScrollView( + dragStartBehavior: DragStartBehavior.down, headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return [ const SliverPersistentHeader( @@ -598,6 +608,7 @@ void main() { ]; }, body: SingleChildScrollView( + dragStartBehavior: DragStartBehavior.down, child: Container( height: 1000.0, child: const Placeholder(key: key2), diff --git a/packages/flutter/test/widgets/page_view_test.dart b/packages/flutter/test/widgets/page_view_test.dart index c8ecdbec21..4c858ad3b7 100644 --- a/packages/flutter/test/widgets/page_view_test.dart +++ b/packages/flutter/test/widgets/page_view_test.dart @@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'semantics_tester.dart'; import 'states.dart'; @@ -19,8 +20,10 @@ void main() { await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: PageView( + dragStartBehavior: DragStartBehavior.down, children: kStates.map((String state) { return GestureDetector( + dragStartBehavior: DragStartBehavior.down, onTap: () { log.add(state); }, diff --git a/packages/flutter/test/widgets/scroll_notification_test.dart b/packages/flutter/test/widgets/scroll_notification_test.dart index 0fbadfad88..b4cdcecddb 100644 --- a/packages/flutter/test/widgets/scroll_notification_test.dart +++ b/packages/flutter/test/widgets/scroll_notification_test.dart @@ -60,6 +60,7 @@ void main() { return false; }, child: SingleChildScrollView( + dragStartBehavior: DragStartBehavior.down, child: SizedBox( height: 1200.0, child: NotificationListener( @@ -70,11 +71,14 @@ void main() { }, child: Container( padding: const EdgeInsets.all(50.0), - child: const SingleChildScrollView(child: SizedBox(height: 1200.0)) - ) - ) - ) - ) + child: const SingleChildScrollView( + child: SizedBox(height: 1200.0), + dragStartBehavior: DragStartBehavior.down, + ), + ), + ), + ), + ), )); final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0)); diff --git a/packages/flutter/test/widgets/scroll_view_test.dart b/packages/flutter/test/widgets/scroll_view_test.dart index 15e161da36..3fcd382337 100644 --- a/packages/flutter/test/widgets/scroll_view_test.dart +++ b/packages/flutter/test/widgets/scroll_view_test.dart @@ -4,6 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/material.dart'; import 'states.dart'; @@ -16,6 +17,7 @@ void main() { Directionality( textDirection: TextDirection.ltr, child: ListView( + dragStartBehavior: DragStartBehavior.down, children: kStates.map((String state) { return GestureDetector( onTap: () { @@ -26,6 +28,7 @@ void main() { color: const Color(0xFF0000FF), child: Text(state), ), + dragStartBehavior: DragStartBehavior.down, ); }).toList(), ), @@ -54,6 +57,7 @@ void main() { return Directionality( textDirection: TextDirection.ltr, child: ListView( + dragStartBehavior: DragStartBehavior.down, children: kStates.take(n).map((String state) { return Container( height: 200.0, @@ -85,11 +89,13 @@ void main() { Directionality( textDirection: TextDirection.ltr, child: CustomScrollView( + dragStartBehavior: DragStartBehavior.down, slivers: [ SliverList( delegate: SliverChildListDelegate( kStates.map((String state) { return GestureDetector( + dragStartBehavior: DragStartBehavior.down, onTap: () { log.add(state); }, diff --git a/packages/flutter/test/widgets/scrollable_fling_test.dart b/packages/flutter/test/widgets/scrollable_fling_test.dart index 2dd30a0410..68b8ba6125 100644 --- a/packages/flutter/test/widgets/scrollable_fling_test.dart +++ b/packages/flutter/test/widgets/scrollable_fling_test.dart @@ -4,6 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; const TextStyle testFont = TextStyle( color: Color(0xFF00FF00), @@ -19,6 +20,7 @@ Future pumpTest(WidgetTester tester, TargetPlatform platform) async { home: Container( color: const Color(0xFF111111), child: ListView.builder( + dragStartBehavior: DragStartBehavior.down, itemBuilder: (BuildContext context, int index) { return Text('$index', style: testFont); }, @@ -64,7 +66,7 @@ void main() { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, - child: ListView(children: textWidgets) + child: ListView(children: textWidgets, dragStartBehavior: DragStartBehavior.down) ), ); @@ -92,7 +94,7 @@ void main() { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, - child: ListView(children: textWidgets) + child: ListView(children: textWidgets, dragStartBehavior: DragStartBehavior.down) ), ); diff --git a/packages/flutter/test/widgets/scrollable_list_hit_testing_test.dart b/packages/flutter/test/widgets/scrollable_list_hit_testing_test.dart index c06da6d576..2f7f0bf2f0 100644 --- a/packages/flutter/test/widgets/scrollable_list_hit_testing_test.dart +++ b/packages/flutter/test/widgets/scrollable_list_hit_testing_test.dart @@ -5,6 +5,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; const List items = [0, 1, 2, 3, 4, 5]; @@ -18,6 +19,7 @@ void main() { child: Container( height: 50.0, child: ListView( + dragStartBehavior: DragStartBehavior.down, itemExtent: 290.0, scrollDirection: Axis.horizontal, children: items.map((int item) { @@ -25,6 +27,7 @@ void main() { child: GestureDetector( onTap: () { tapped.add(item); }, child: Text('$item'), + dragStartBehavior: DragStartBehavior.down, ), ); }).toList(), @@ -60,6 +63,7 @@ void main() { child: Container( width: 50.0, child: ListView( + dragStartBehavior: DragStartBehavior.down, itemExtent: 290.0, scrollDirection: Axis.vertical, children: items.map((int item) { @@ -67,6 +71,7 @@ void main() { child: GestureDetector( onTap: () { tapped.add(item); }, child: Text('$item'), + dragStartBehavior: DragStartBehavior.down, ), ); }).toList(), diff --git a/packages/flutter/test/widgets/scrollable_semantics_test.dart b/packages/flutter/test/widgets/scrollable_semantics_test.dart index 2dcf7e6c26..986eb6be89 100644 --- a/packages/flutter/test/widgets/scrollable_semantics_test.dart +++ b/packages/flutter/test/widgets/scrollable_semantics_test.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'semantics_tester.dart'; @@ -266,6 +267,7 @@ void main() { await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ListView.builder( + dragStartBehavior: DragStartBehavior.down, itemExtent: 20.0, itemBuilder: (BuildContext context, int index) { return Text('entry $index'); diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart index 8b77bb2646..bfefa12213 100644 --- a/packages/flutter/test/widgets/widget_inspector_test.dart +++ b/packages/flutter/test/widgets/widget_inspector_test.dart @@ -12,6 +12,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; // Start of block of code where widget creation location line numbers and // columns will impact whether tests pass. @@ -396,6 +397,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { key: inspectorKey, selectButtonBuilder: selectButtonBuilder, child: ListView( + dragStartBehavior: DragStartBehavior.down, children: [ Container( key: childKey, @@ -1509,7 +1511,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { _CreationLocation location = knownLocations[id]; expect(location.file, equals(file)); // ClockText widget. - expect(location.line, equals(49)); + expect(location.line, equals(50)); expect(location.column, equals(9)); expect(count, equals(1)); @@ -1518,7 +1520,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { location = knownLocations[id]; expect(location.file, equals(file)); // Text widget in _ClockTextState build method. - expect(location.line, equals(87)); + expect(location.line, equals(88)); expect(location.column, equals(12)); expect(count, equals(1)); @@ -1543,7 +1545,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { location = knownLocations[id]; expect(location.file, equals(file)); // ClockText widget. - expect(location.line, equals(49)); + expect(location.line, equals(50)); expect(location.column, equals(9)); expect(count, equals(3)); // 3 clock widget instances rebuilt. @@ -1552,7 +1554,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { location = knownLocations[id]; expect(location.file, equals(file)); // Text widget in _ClockTextState build method. - expect(location.line, equals(87)); + expect(location.line, equals(88)); expect(location.column, equals(12)); expect(count, equals(3)); // 3 clock widget instances rebuilt.