Unverified Commit cea4aa9b authored by jslavitz's avatar jslavitz Committed by GitHub

Teach drag start behaviors to DragGestureRecognizer (#26246)

* the onStart callback will report the location of the pointer where it wins the gesture arena by default instead of the pointer down location. Fixes all tests related to changing this default value.
parent 843f2620
......@@ -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<DrawerDemo> 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<DrawerDemo> with TickerProviderStateMixin {
),
otherAccountsPictures: <Widget>[
GestureDetector(
dragStartBehavior: DragStartBehavior.down,
onTap: () {
_onOtherAccountsTap(context);
},
......@@ -120,6 +123,7 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
),
),
GestureDetector(
dragStartBehavior: DragStartBehavior.down,
onTap: () {
_onOtherAccountsTap(context);
},
......@@ -149,6 +153,7 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
removeTop: true,
child: Expanded(
child: ListView(
dragStartBehavior: DragStartBehavior.down,
padding: const EdgeInsets.only(top: 8.0),
children: <Widget>[
Stack(
......
......@@ -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<PasswordField> {
labelText: widget.labelText,
helperText: widget.helperText,
suffixIcon: GestureDetector(
dragStartBehavior: DragStartBehavior.down,
onTap: () {
setState(() {
_obscureText = !_obscureText;
......@@ -167,6 +169,7 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
@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<TextFormFieldDemo> {
autovalidate: _autovalidate,
onWillPop: _warnUserAboutInvalidData,
child: SingleChildScrollView(
dragStartBehavior: DragStartBehavior.down,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
......
......@@ -8,6 +8,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'backdrop.dart';
import 'demos.dart';
......@@ -256,6 +257,7 @@ class _DemosPage extends StatelessWidget {
label: category.name,
explicitChildNodes: true,
child: ListView(
dragStartBehavior: DragStartBehavior.down,
key: PageStorageKey<String>(category.name),
padding: EdgeInsets.only(top: 8.0, bottom: windowBottomPadding),
children: kGalleryCategoryToDemos[category].map<Widget>((GalleryDemo demo) {
......
......@@ -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<StockHome> {
Widget _buildDrawer(BuildContext context) {
return Drawer(
child: ListView(
dragStartBehavior: DragStartBehavior.down,
children: <Widget>[
const DrawerHeader(child: Center(child: Text('Stocks'))),
const ListTile(
......@@ -317,11 +319,13 @@ class StockHomeState extends State<StockHome> {
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: <Widget>[
_buildStockTab(context, StockHomeTab.market, widget.stocks.allSymbols),
_buildStockTab(context, StockHomeTab.portfolio, portfolioSymbols),
......
......@@ -52,12 +52,16 @@ import 'thumb_painter.dart';
/// * <https://developer.apple.com/ios/human-interface-guidelines/controls/switches/>
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<CupertinoSwitch> 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<bool> 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<bool> 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;
......
......@@ -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<void>('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<DragStartBehavior>('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) {
......
......@@ -24,6 +24,24 @@ export 'pointer_router.dart' show PointerRouter;
/// anonymous functions that return objects of particular types.
typedef RecognizerCallback<T> = 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
......
......@@ -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,24 @@ class DayPicker extends StatelessWidget {
/// Optional user supplied predicate function to customize selectable days.
final SelectableDayPredicate selectableDayPredicate;
/// Determines the way that drag start behavior is handled.
///
/// If set to [DragStartBehavior.start], the drag gesture used to scroll a
/// date picker wheel will begin upon the detection of a drag gesture. If set
/// to [DragStartBehavior.down] it will begin when a down event is first
/// detected.
///
/// 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;
/// 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 +463,7 @@ class DayPicker extends StatelessWidget {
onChanged(dayToBuild);
},
child: dayWidget,
dragStartBehavior: dragStartBehavior,
);
}
......@@ -502,6 +524,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 +548,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 +635,7 @@ class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStat
lastDate: widget.lastDate,
displayedMonth: month,
selectableDayPredicate: widget.selectableDayPredicate,
dragStartBehavior: widget.dragStartBehavior,
);
}
......@@ -669,6 +696,7 @@ class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStat
return false;
},
child: PageView.builder(
dragStartBehavior: widget.dragStartBehavior,
key: ValueKey<DateTime>(widget.selectedDate),
controller: _dayPickerController,
scrollDirection: Axis.horizontal,
......@@ -759,6 +787,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 +807,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 +833,7 @@ class _YearPickerState extends State<YearPicker> {
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,
......
......@@ -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<DrawerController> with SingleTickerPro
onHorizontalDragEnd: _settle,
behavior: HitTestBehavior.translucent,
excludeFromSemantics: true,
dragStartBehavior: widget.dragStartBehavior,
child: Container(width: dragAreaWidth),
),
);
......@@ -410,6 +434,7 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro
onHorizontalDragEnd: _settle,
onHorizontalDragCancel: _handleDragCancel,
excludeFromSemantics: true,
dragStartBehavior: widget.dragStartBehavior,
child: RepaintBoundary(
child: Stack(
children: <Widget>[
......
......@@ -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 <int>[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<PaginatedDataTable> {
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
dragStartBehavior: widget.dragStartBehavior,
child: DataTable(
key: _tableKey,
columns: widget.columns,
......@@ -435,6 +442,7 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
child: Container(
height: 56.0,
child: SingleChildScrollView(
dragStartBehavior: widget.dragStartBehavior,
scrollDirection: Axis.horizontal,
reverse: true,
child: Row(
......
......@@ -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<Scaffold> 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<Scaffold> 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
......
......@@ -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<Switch> with TickerProviderStateMixin {
}
return _SwitchRenderObjectWidget(
dragStartBehavior: widget.dragStartBehavior,
value: widget.value,
activeColor: activeThumbColor,
inactiveColor: inactiveThumbColor,
......@@ -240,6 +247,7 @@ class _SwitchState extends State<Switch> 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<bool> 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<bool> 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();
......
......@@ -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<TabBar> {
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<TabBarView> {
return NotificationListener<ScrollNotification>(
onNotification: _handleScrollNotification,
child: PageView(
dragStartBehavior: widget.dragStartBehavior,
controller: _pageController,
physics: widget.physics == null ? _kTabBarViewPhysics : _kTabBarViewPhysics.applyTo(widget.physics),
children: _children,
......
......@@ -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<TextField> with AutomaticKeepAliveClientMixi
scrollPadding: widget.scrollPadding,
keyboardAppearance: keyboardAppearance,
enableInteractiveSelection: widget.enableInteractiveSelection,
dragStartBehavior: widget.dragStartBehavior,
),
);
......
......@@ -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 <DismissDirection, double>{},
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<Dismissible> with TickerProviderStateMixin
Tween<Offset>(
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<Dismissible> 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<Dismissible> with TickerProviderStateMixin
onVerticalDragUpdate: _directionIsXAxis ? null : _handleDragUpdate,
onVerticalDragEnd: _directionIsXAxis ? null : _handleDragEnd,
behavior: HitTestBehavior.opaque,
child: content
child: content,
dragStartBehavior: widget.dragStartBehavior,
);
}
}
......
......@@ -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<EditableText> 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<EditableText> 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,
......
......@@ -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,27 @@ 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].
///
/// Only the [onStart] callbacks for the [VerticalDragGestureRecognizer],
/// [HorizontalDragGestureRecognizer] and [PanGestureRecognizer] are affected
/// by this setting.
///
/// See also:
///
/// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
final DragStartBehavior dragStartBehavior;
@override
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
......@@ -421,7 +444,8 @@ class GestureDetector extends StatelessWidget {
..onStart = onVerticalDragStart
..onUpdate = onVerticalDragUpdate
..onEnd = onVerticalDragEnd
..onCancel = onVerticalDragCancel;
..onCancel = onVerticalDragCancel
..dragStartBehavior = dragStartBehavior;
},
);
}
......@@ -439,7 +463,8 @@ class GestureDetector extends StatelessWidget {
..onStart = onHorizontalDragStart
..onUpdate = onHorizontalDragUpdate
..onEnd = onHorizontalDragEnd
..onCancel = onHorizontalDragCancel;
..onCancel = onHorizontalDragCancel
..dragStartBehavior = dragStartBehavior;
},
);
}
......@@ -457,7 +482,8 @@ class GestureDetector extends StatelessWidget {
..onStart = onPanStart
..onUpdate = onPanUpdate
..onEnd = onPanEnd
..onCancel = onPanCancel;
..onCancel = onPanCancel
..dragStartBehavior = dragStartBehavior;
},
);
}
......@@ -497,6 +523,11 @@ class GestureDetector extends StatelessWidget {
child: child,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(EnumProperty<DragStartBehavior>('startBehavior', dragStartBehavior));
}
}
/// A widget that detects gestures described by the given gesture
......@@ -550,7 +581,7 @@ class RawGestureDetector extends StatefulWidget {
this.child,
this.gestures = const <Type, GestureRecognizerFactory>{},
this.behavior,
this.excludeFromSemantics = false
this.excludeFromSemantics = false,
}) : assert(gestures != null),
assert(excludeFromSemantics != null),
super(key: key);
......
......@@ -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<Widget> 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<NestedScrollView> {
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<Widget> slivers,
@required this.handle,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
}) : super(
scrollDirection: scrollDirection,
reverse: reverse,
physics: physics,
controller: controller,
slivers: slivers,
dragStartBehavior: dragStartBehavior,
);
final SliverOverlapAbsorberHandle handle;
......
......@@ -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<Widget> children = const <Widget>[],
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<PageView> {
return false;
},
child: Scrollable(
dragStartBehavior: widget.dragStartBehavior,
axisDirection: axisDirection,
controller: widget.controller,
physics: physics,
......
......@@ -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 <Widget>[],
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<Widget> children = const <Widget>[],
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<Widget> children = const <Widget>[],
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<Widget> children = const <Widget>[],
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].
......
......@@ -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<Scrollable> 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<Scrollable> with TickerProviderStateMixin
..onCancel = _handleDragCancel
..minFlingDistance = _physics?.minFlingDistance
..minFlingVelocity = _physics?.minFlingVelocity
..maxFlingVelocity = _physics?.maxFlingVelocity;
..maxFlingVelocity = _physics?.maxFlingVelocity
..dragStartBehavior = widget.dragStartBehavior;
},
),
};
......
......@@ -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,
......
......@@ -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<TextSelection> 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,
......
......@@ -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;
......
......@@ -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)));
......
......@@ -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: <Widget>[
Container(height: 500.0, child: InkWell(onTap: () { }, child: const Placeholder())),
Container(height: 500.0),
......
......@@ -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 <int>[ 5 ],
onRowsPerPageChanged: (int rowsPerPage) { },
columns: const <DataColumn>[
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 <int>[ 5 ],
onRowsPerPageChanged: (int rowsPerPage) { },
columns: const <DataColumn>[
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(
......
......@@ -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<Widget>.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(
......
......@@ -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(() {
......
......@@ -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),
......
......@@ -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){
......
......@@ -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<int>(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: <int>[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,
......
......@@ -383,6 +383,7 @@ void main() {
await tester.pumpWidget(MaterialApp(
home: ListView(
dragStartBehavior: DragStartBehavior.down,
children: <Widget>[
DragTarget<int>(
builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
......@@ -489,6 +490,7 @@ void main() {
await tester.pumpWidget(MaterialApp(
home: ListView(
dragStartBehavior: DragStartBehavior.down,
scrollDirection: Axis.horizontal,
children: <Widget>[
DragTarget<int>(
......
......@@ -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(
......
......@@ -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'); },
......
......@@ -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 <Widget>[],
),
......@@ -28,9 +30,11 @@ void main() {
Directionality(
textDirection: TextDirection.ltr,
child: GridView.count(
dragStartBehavior: DragStartBehavior.down,
crossAxisCount: 4,
children: kStates.map<Widget>((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<Widget>((String state) {
return GestureDetector(
dragStartBehavior: DragStartBehavior.down,
onTap: () {
log.add(state);
},
......
......@@ -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();
......
......@@ -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(
......
......@@ -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 <Widget>[
......@@ -79,6 +82,7 @@ Widget buildTest({ ScrollController controller, String title ='TTTTTTTT' }) {
],
),
ListView(
dragStartBehavior: DragStartBehavior.down,
children: <Widget>[
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: <Widget>[
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<Widget>((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<Widget>((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<String>(name),
dragStartBehavior: DragStartBehavior.down,
slivers: <Widget>[
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 <Widget>[
const SliverPersistentHeader(
......@@ -598,6 +608,7 @@ void main() {
];
},
body: SingleChildScrollView(
dragStartBehavior: DragStartBehavior.down,
child: Container(
height: 1000.0,
child: const Placeholder(key: key2),
......
......@@ -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<Widget>((String state) {
return GestureDetector(
dragStartBehavior: DragStartBehavior.down,
onTap: () {
log.add(state);
},
......
......@@ -60,6 +60,7 @@ void main() {
return false;
},
child: SingleChildScrollView(
dragStartBehavior: DragStartBehavior.down,
child: SizedBox(
height: 1200.0,
child: NotificationListener<ScrollNotification>(
......@@ -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));
......
......@@ -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<Widget>((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<Widget>((String state) {
return Container(
height: 200.0,
......@@ -85,11 +89,13 @@ void main() {
Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
dragStartBehavior: DragStartBehavior.down,
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate(
kStates.map<Widget>((String state) {
return GestureDetector(
dragStartBehavior: DragStartBehavior.down,
onTap: () {
log.add(state);
},
......
......@@ -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<void> 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)
),
);
......
......@@ -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<int> items = <int>[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<Widget>((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<Widget>((int item) {
......@@ -67,6 +71,7 @@ void main() {
child: GestureDetector(
onTap: () { tapped.add(item); },
child: Text('$item'),
dragStartBehavior: DragStartBehavior.down,
),
);
}).toList(),
......
......@@ -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');
......
......@@ -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: <Widget>[
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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment