Unverified Commit ee0b67dd authored by Gabriel Gava's avatar Gabriel Gava Committed by GitHub

Exposed EdgeDraggingAutoScroller velocityScalar to ReorderableList (#124459)

Exposed EdgeDraggingAutoScroller velocityScalar to ReorderableList
parent 895879c1
...@@ -83,6 +83,7 @@ class ReorderableListView extends StatefulWidget { ...@@ -83,6 +83,7 @@ class ReorderableListView extends StatefulWidget {
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.restorationId, this.restorationId,
this.clipBehavior = Clip.hardEdge, this.clipBehavior = Clip.hardEdge,
this.autoScrollerVelocityScalar,
}) : assert( }) : assert(
itemExtent == null || prototypeItem == null, itemExtent == null || prototypeItem == null,
'You can only pass itemExtent or prototypeItem, not both', 'You can only pass itemExtent or prototypeItem, not both',
...@@ -148,6 +149,7 @@ class ReorderableListView extends StatefulWidget { ...@@ -148,6 +149,7 @@ class ReorderableListView extends StatefulWidget {
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.restorationId, this.restorationId,
this.clipBehavior = Clip.hardEdge, this.clipBehavior = Clip.hardEdge,
this.autoScrollerVelocityScalar,
}) : assert(itemCount >= 0), }) : assert(itemCount >= 0),
assert( assert(
itemExtent == null || prototypeItem == null, itemExtent == null || prototypeItem == null,
...@@ -259,6 +261,9 @@ class ReorderableListView extends StatefulWidget { ...@@ -259,6 +261,9 @@ class ReorderableListView extends StatefulWidget {
/// {@macro flutter.widgets.list_view.prototypeItem} /// {@macro flutter.widgets.list_view.prototypeItem}
final Widget? prototypeItem; final Widget? prototypeItem;
/// {@macro flutter.widgets.EdgeDraggingAutoScroller.velocityScalar}
final double? autoScrollerVelocityScalar;
@override @override
State<ReorderableListView> createState() => _ReorderableListViewState(); State<ReorderableListView> createState() => _ReorderableListViewState();
} }
...@@ -428,6 +433,7 @@ class _ReorderableListViewState extends State<ReorderableListView> { ...@@ -428,6 +433,7 @@ class _ReorderableListViewState extends State<ReorderableListView> {
onReorderStart: widget.onReorderStart, onReorderStart: widget.onReorderStart,
onReorderEnd: widget.onReorderEnd, onReorderEnd: widget.onReorderEnd,
proxyDecorator: widget.proxyDecorator ?? _proxyDecorator, proxyDecorator: widget.proxyDecorator ?? _proxyDecorator,
autoScrollerVelocityScalar: widget.autoScrollerVelocityScalar,
), ),
), ),
if (widget.footer != null) if (widget.footer != null)
......
...@@ -133,6 +133,7 @@ class ReorderableList extends StatefulWidget { ...@@ -133,6 +133,7 @@ class ReorderableList extends StatefulWidget {
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.restorationId, this.restorationId,
this.clipBehavior = Clip.hardEdge, this.clipBehavior = Clip.hardEdge,
this.autoScrollerVelocityScalar,
}) : assert(itemCount >= 0), }) : assert(itemCount >= 0),
assert( assert(
itemExtent == null || prototypeItem == null, itemExtent == null || prototypeItem == null,
...@@ -255,6 +256,9 @@ class ReorderableList extends StatefulWidget { ...@@ -255,6 +256,9 @@ class ReorderableList extends StatefulWidget {
/// {@macro flutter.widgets.list_view.prototypeItem} /// {@macro flutter.widgets.list_view.prototypeItem}
final Widget? prototypeItem; final Widget? prototypeItem;
/// {@macro flutter.widgets.EdgeDraggingAutoScroller.velocityScalar}
final double? autoScrollerVelocityScalar;
/// The state from the closest instance of this class that encloses the given /// The state from the closest instance of this class that encloses the given
/// context. /// context.
/// ///
...@@ -400,6 +404,7 @@ class ReorderableListState extends State<ReorderableList> { ...@@ -400,6 +404,7 @@ class ReorderableListState extends State<ReorderableList> {
onReorderStart: widget.onReorderStart, onReorderStart: widget.onReorderStart,
onReorderEnd: widget.onReorderEnd, onReorderEnd: widget.onReorderEnd,
proxyDecorator: widget.proxyDecorator, proxyDecorator: widget.proxyDecorator,
autoScrollerVelocityScalar: widget.autoScrollerVelocityScalar,
), ),
), ),
], ],
...@@ -445,6 +450,7 @@ class SliverReorderableList extends StatefulWidget { ...@@ -445,6 +450,7 @@ class SliverReorderableList extends StatefulWidget {
this.itemExtent, this.itemExtent,
this.prototypeItem, this.prototypeItem,
this.proxyDecorator, this.proxyDecorator,
this.autoScrollerVelocityScalar,
}) : assert(itemCount >= 0), }) : assert(itemCount >= 0),
assert( assert(
itemExtent == null || prototypeItem == null, itemExtent == null || prototypeItem == null,
...@@ -478,6 +484,9 @@ class SliverReorderableList extends StatefulWidget { ...@@ -478,6 +484,9 @@ class SliverReorderableList extends StatefulWidget {
/// {@macro flutter.widgets.list_view.prototypeItem} /// {@macro flutter.widgets.list_view.prototypeItem}
final Widget? prototypeItem; final Widget? prototypeItem;
/// {@macro flutter.widgets.EdgeDraggingAutoScroller.velocityScalar}
final double? autoScrollerVelocityScalar;
@override @override
SliverReorderableListState createState() => SliverReorderableListState(); SliverReorderableListState createState() => SliverReorderableListState();
...@@ -613,7 +622,8 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke ...@@ -613,7 +622,8 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
_autoScroller?.stopAutoScroll(); _autoScroller?.stopAutoScroll();
_autoScroller = EdgeDraggingAutoScroller( _autoScroller = EdgeDraggingAutoScroller(
_scrollable, _scrollable,
onScrollViewScrolled: _handleScrollableAutoScrolled onScrollViewScrolled: _handleScrollableAutoScrolled,
velocityScalar: widget.autoScrollerVelocityScalar,
); );
} }
} }
...@@ -624,6 +634,15 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke ...@@ -624,6 +634,15 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
if (widget.itemCount != oldWidget.itemCount) { if (widget.itemCount != oldWidget.itemCount) {
cancelReorder(); cancelReorder();
} }
if (widget.autoScrollerVelocityScalar != oldWidget.autoScrollerVelocityScalar) {
_autoScroller?.stopAutoScroll();
_autoScroller = EdgeDraggingAutoScroller(
_scrollable,
onScrollViewScrolled: _handleScrollableAutoScrolled,
velocityScalar: widget.autoScrollerVelocityScalar,
);
}
} }
@override @override
......
...@@ -160,8 +160,8 @@ class EdgeDraggingAutoScroller { ...@@ -160,8 +160,8 @@ class EdgeDraggingAutoScroller {
EdgeDraggingAutoScroller( EdgeDraggingAutoScroller(
this.scrollable, { this.scrollable, {
this.onScrollViewScrolled, this.onScrollViewScrolled,
this.velocityScalar = _kDefaultAutoScrollVelocityScalar, double? velocityScalar,
}); }): velocityScalar = velocityScalar ?? _kDefaultAutoScrollVelocityScalar;
// An eyeballed value for a smooth scrolling experience. // An eyeballed value for a smooth scrolling experience.
static const double _kDefaultAutoScrollVelocityScalar = 7; static const double _kDefaultAutoScrollVelocityScalar = 7;
...@@ -176,10 +176,14 @@ class EdgeDraggingAutoScroller { ...@@ -176,10 +176,14 @@ class EdgeDraggingAutoScroller {
/// in between each scroll. /// in between each scroll.
final VoidCallback? onScrollViewScrolled; final VoidCallback? onScrollViewScrolled;
/// {@template flutter.widgets.EdgeDraggingAutoScroller.velocityScalar}
/// The velocity scalar per pixel over scroll. /// The velocity scalar per pixel over scroll.
/// ///
/// It represents how the velocity scale with the over scroll distance. The /// It represents how the velocity scale with the over scroll distance. The
/// auto-scroll velocity = <distance of overscroll> * velocityScalar. /// auto-scroll velocity = <distance of overscroll> * velocityScalar.
///
/// Defaults to 7 if not set or set to null.
/// {@endtemplate}
final double velocityScalar; final double velocityScalar;
late Rect _dragTargetRelatedToScrollOrigin; late Rect _dragTargetRelatedToScrollOrigin;
......
...@@ -1779,6 +1779,88 @@ void main() { ...@@ -1779,6 +1779,88 @@ void main() {
expect(item1Height, 30.0); expect(item1Height, 30.0);
expect(item2Height, 30.0); expect(item2Height, 30.0);
}); });
testWidgets('ReorderableListView auto scrolls speed is configurable', (WidgetTester tester) async {
Future<void> pumpFor({
required Duration duration,
Duration interval = const Duration(milliseconds: 50),
}) async {
await tester.pump();
int times = (duration.inMilliseconds / interval.inMilliseconds).ceil();
while (times > 0) {
await tester.pump(interval + const Duration(milliseconds: 1));
await tester.idle();
times--;
}
}
Future<double> pumpListAndDrag({required double autoScrollerVelocityScalar}) async {
final List<int> items = List<int>.generate(10, (int index) => index);
final ScrollController scrollController = ScrollController();
await tester.pumpWidget(
MaterialApp(
home: ReorderableListView.builder(
itemBuilder: (BuildContext context, int index) {
return Container(
key: ValueKey<int>(items[index]),
height: 100,
color: items[index].isOdd ? Colors.red : Colors.green,
child: ReorderableDragStartListener(
index: index,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('item ${items[index]}'),
const Icon(Icons.drag_handle),
],
),
),
);
},
itemCount: items.length,
onReorder: (int fromIndex, int toIndex) {},
scrollController: scrollController,
autoScrollerVelocityScalar: autoScrollerVelocityScalar,
),
),
);
expect(scrollController.offset, 0);
final Finder item = find.text('item 0');
final TestGesture drag = await tester.startGesture(tester.getCenter(item));
// Drag just enough to touch the edge but not surpass it, so the
// auto scroller is not yet triggered
await drag.moveBy(const Offset(0, 500));
await pumpFor(duration: const Duration(milliseconds: 200));
expect(scrollController.offset, 0);
// Now drag a little bit more so the auto scroller triggers
await drag.moveBy(const Offset(0, 50));
await pumpFor(
duration: const Duration(milliseconds: 600),
interval: Duration(milliseconds: (1000 / autoScrollerVelocityScalar).round()),
);
return scrollController.offset;
}
const double fastVelocityScalar = 20;
final double offsetForFastScroller = await pumpListAndDrag(autoScrollerVelocityScalar: fastVelocityScalar);
// Reset widget tree
await tester.pumpWidget(const SizedBox());
const double slowVelocityScalar = 5;
final double offsetForSlowScroller = await pumpListAndDrag(autoScrollerVelocityScalar: slowVelocityScalar);
expect(offsetForFastScroller / offsetForSlowScroller, fastVelocityScalar / slowVelocityScalar);
});
} }
Future<void> longPressDrag(WidgetTester tester, Offset start, Offset end) async { Future<void> longPressDrag(WidgetTester tester, Offset start, Offset end) async {
......
...@@ -1223,6 +1223,91 @@ void main() { ...@@ -1223,6 +1223,91 @@ void main() {
expect(item0, findsNothing); expect(item0, findsNothing);
}); });
testWidgets('SliverReorderableList auto scrolls speed is configurable', (WidgetTester tester) async {
Future<void> pumpFor({
required Duration duration,
Duration interval = const Duration(milliseconds: 50),
}) async {
await tester.pump();
int times = (duration.inMilliseconds / interval.inMilliseconds).ceil();
while (times > 0) {
await tester.pump(interval + const Duration(milliseconds: 1));
await tester.idle();
times--;
}
}
Future<double> pumpListAndDrag({required double autoScrollerVelocityScalar}) async {
final List<int> items = List<int>.generate(10, (int index) => index);
final ScrollController scrollController = ScrollController();
await tester.pumpWidget(
MaterialApp(
home: CustomScrollView(
controller: scrollController,
slivers: <Widget>[
SliverReorderableList(
itemBuilder: (BuildContext context, int index) {
return Container(
key: ValueKey<int>(items[index]),
height: 100,
color: items[index].isOdd ? Colors.red : Colors.green,
child: ReorderableDragStartListener(
index: index,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('item ${items[index]}'),
const Icon(Icons.drag_handle),
],
),
),
);
},
itemCount: items.length,
onReorder: (int fromIndex, int toIndex) {},
autoScrollerVelocityScalar: autoScrollerVelocityScalar,
),
],
),
),
);
expect(scrollController.offset, 0);
final Finder item = find.text('item 0');
final TestGesture drag = await tester.startGesture(tester.getCenter(item));
// Drag just enough to touch the edge but not surpass it, so the
// auto scroller is not yet triggered
await drag.moveBy(const Offset(0, 500));
await pumpFor(duration: const Duration(milliseconds: 200));
expect(scrollController.offset, 0);
// Now drag a little bit more so the auto scroller triggers
await drag.moveBy(const Offset(0, 50));
await pumpFor(
duration: const Duration(milliseconds: 600),
interval: Duration(milliseconds: (1000 / autoScrollerVelocityScalar).round()),
);
return scrollController.offset;
}
const double fastVelocityScalar = 20;
final double offsetForFastScroller = await pumpListAndDrag(autoScrollerVelocityScalar: fastVelocityScalar);
// Reset widget tree
await tester.pumpWidget(const SizedBox());
const double slowVelocityScalar = 5;
final double offsetForSlowScroller = await pumpListAndDrag(autoScrollerVelocityScalar: slowVelocityScalar);
expect(offsetForFastScroller / offsetForSlowScroller, fastVelocityScalar / slowVelocityScalar);
});
} }
class TestList extends StatelessWidget { class TestList extends StatelessWidget {
...@@ -1235,6 +1320,7 @@ class TestList extends StatelessWidget { ...@@ -1235,6 +1320,7 @@ class TestList extends StatelessWidget {
this.reverse = false, this.reverse = false,
this.onReorderStart, this.onReorderStart,
this.onReorderEnd, this.onReorderEnd,
this.autoScrollerVelocityScalar,
}); });
final List<int> items; final List<int> items;
...@@ -1243,6 +1329,7 @@ class TestList extends StatelessWidget { ...@@ -1243,6 +1329,7 @@ class TestList extends StatelessWidget {
final ReorderItemProxyDecorator? proxyDecorator; final ReorderItemProxyDecorator? proxyDecorator;
final bool reverse; final bool reverse;
final void Function(int)? onReorderStart, onReorderEnd; final void Function(int)? onReorderStart, onReorderEnd;
final double? autoScrollerVelocityScalar;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -1288,6 +1375,7 @@ class TestList extends StatelessWidget { ...@@ -1288,6 +1375,7 @@ class TestList extends StatelessWidget {
proxyDecorator: proxyDecorator, proxyDecorator: proxyDecorator,
onReorderStart: onReorderStart, onReorderStart: onReorderStart,
onReorderEnd: onReorderEnd, onReorderEnd: onReorderEnd,
autoScrollerVelocityScalar: autoScrollerVelocityScalar,
), ),
], ],
); );
......
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