Unverified Commit 922bbfee authored by Viren Khatri's avatar Viren Khatri Committed by GitHub

Adds `onReorderStart` and `onReorderEnd` arguments to `ReorderableList`. (#96049)

parent 3e4a7c0d
...@@ -64,6 +64,8 @@ class ReorderableListView extends StatefulWidget { ...@@ -64,6 +64,8 @@ class ReorderableListView extends StatefulWidget {
Key? key, Key? key,
required List<Widget> children, required List<Widget> children,
required this.onReorder, required this.onReorder,
this.onReorderStart,
this.onReorderEnd,
this.itemExtent, this.itemExtent,
this.prototypeItem, this.prototypeItem,
this.proxyDecorator, this.proxyDecorator,
...@@ -131,6 +133,8 @@ class ReorderableListView extends StatefulWidget { ...@@ -131,6 +133,8 @@ class ReorderableListView extends StatefulWidget {
required this.itemBuilder, required this.itemBuilder,
required this.itemCount, required this.itemCount,
required this.onReorder, required this.onReorder,
this.onReorderStart,
this.onReorderEnd,
this.itemExtent, this.itemExtent,
this.prototypeItem, this.prototypeItem,
this.proxyDecorator, this.proxyDecorator,
...@@ -168,6 +172,12 @@ class ReorderableListView extends StatefulWidget { ...@@ -168,6 +172,12 @@ class ReorderableListView extends StatefulWidget {
/// {@macro flutter.widgets.reorderable_list.onReorder} /// {@macro flutter.widgets.reorderable_list.onReorder}
final ReorderCallback onReorder; final ReorderCallback onReorder;
/// {@macro flutter.widgets.reorderable_list.onReorderStart}
final void Function(int index)? onReorderStart;
/// {@macro flutter.widgets.reorderable_list.onReorderEnd}
final void Function(int index)? onReorderEnd;
/// {@macro flutter.widgets.reorderable_list.proxyDecorator} /// {@macro flutter.widgets.reorderable_list.proxyDecorator}
final ReorderItemProxyDecorator? proxyDecorator; final ReorderItemProxyDecorator? proxyDecorator;
...@@ -479,6 +489,8 @@ class _ReorderableListViewState extends State<ReorderableListView> { ...@@ -479,6 +489,8 @@ class _ReorderableListViewState extends State<ReorderableListView> {
prototypeItem: widget.prototypeItem, prototypeItem: widget.prototypeItem,
itemCount: widget.itemCount, itemCount: widget.itemCount,
onReorder: widget.onReorder, onReorder: widget.onReorder,
onReorderStart: widget.onReorderStart,
onReorderEnd: widget.onReorderEnd,
proxyDecorator: widget.proxyDecorator ?? _proxyDecorator, proxyDecorator: widget.proxyDecorator ?? _proxyDecorator,
), ),
), ),
......
...@@ -114,6 +114,8 @@ class ReorderableList extends StatefulWidget { ...@@ -114,6 +114,8 @@ class ReorderableList extends StatefulWidget {
required this.itemBuilder, required this.itemBuilder,
required this.itemCount, required this.itemCount,
required this.onReorder, required this.onReorder,
this.onReorderStart,
this.onReorderEnd,
this.itemExtent, this.itemExtent,
this.prototypeItem, this.prototypeItem,
this.proxyDecorator, this.proxyDecorator,
...@@ -166,6 +168,34 @@ class ReorderableList extends StatefulWidget { ...@@ -166,6 +168,34 @@ class ReorderableList extends StatefulWidget {
/// {@endtemplate} /// {@endtemplate}
final ReorderCallback onReorder; final ReorderCallback onReorder;
/// {@template flutter.widgets.reorderable_list.onReorderStart}
/// A callback that is called when an item drag has started.
///
/// The index parameter of the callback is the index of the selected item.
///
/// See also:
///
/// * [onReorderEnd], which is a called when the dragged item is dropped.
/// * [onReorder], which reports that a list item has been dragged to a new
/// location.
/// {@endtemplate}
final void Function(int index)? onReorderStart;
/// {@template flutter.widgets.reorderable_list.onReorderEnd}
/// A callback that is called when the dragged item is dropped.
///
/// The index parameter of the callback is the index where the item is
/// dropped. Unlike [onReorder], this is called even when the list item is
/// dropped in the same location.
///
/// See also:
///
/// * [onReorderStart], which is a called when an item drag has started.
/// * [onReorder], which reports that a list item has been dragged to a new
/// location.
/// {@endtemplate}
final void Function(int index)? onReorderEnd;
/// {@template flutter.widgets.reorderable_list.proxyDecorator} /// {@template flutter.widgets.reorderable_list.proxyDecorator}
/// A callback that allows the app to add an animated decoration around /// A callback that allows the app to add an animated decoration around
/// an item when it is being dragged. /// an item when it is being dragged.
...@@ -360,6 +390,8 @@ class ReorderableListState extends State<ReorderableList> { ...@@ -360,6 +390,8 @@ class ReorderableListState extends State<ReorderableList> {
itemBuilder: widget.itemBuilder, itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount, itemCount: widget.itemCount,
onReorder: widget.onReorder, onReorder: widget.onReorder,
onReorderStart: widget.onReorderStart,
onReorderEnd: widget.onReorderEnd,
proxyDecorator: widget.proxyDecorator, proxyDecorator: widget.proxyDecorator,
), ),
), ),
...@@ -400,6 +432,8 @@ class SliverReorderableList extends StatefulWidget { ...@@ -400,6 +432,8 @@ class SliverReorderableList extends StatefulWidget {
required this.itemBuilder, required this.itemBuilder,
required this.itemCount, required this.itemCount,
required this.onReorder, required this.onReorder,
this.onReorderStart,
this.onReorderEnd,
this.itemExtent, this.itemExtent,
this.prototypeItem, this.prototypeItem,
this.proxyDecorator, this.proxyDecorator,
...@@ -419,6 +453,12 @@ class SliverReorderableList extends StatefulWidget { ...@@ -419,6 +453,12 @@ class SliverReorderableList extends StatefulWidget {
/// {@macro flutter.widgets.reorderable_list.onReorder} /// {@macro flutter.widgets.reorderable_list.onReorder}
final ReorderCallback onReorder; final ReorderCallback onReorder;
/// {@macro flutter.widgets.reorderable_list.onReorderStart}
final void Function(int)? onReorderStart;
/// {@macro flutter.widgets.reorderable_list.onReorderEnd}
final void Function(int)? onReorderEnd;
/// {@macro flutter.widgets.reorderable_list.proxyDecorator} /// {@macro flutter.widgets.reorderable_list.proxyDecorator}
final ReorderItemProxyDecorator? proxyDecorator; final ReorderItemProxyDecorator? proxyDecorator;
...@@ -640,6 +680,7 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke ...@@ -640,6 +680,7 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
assert(_dragInfo == null); assert(_dragInfo == null);
final _ReorderableItemState item = _items[_dragIndex!]!; final _ReorderableItemState item = _items[_dragIndex!]!;
item.dragging = true; item.dragging = true;
widget.onReorderStart?.call(_dragIndex!);
item.rebuild(); item.rebuild();
_dragStartTransitionComplete = false; _dragStartTransitionComplete = false;
SchedulerBinding.instance.addPostFrameCallback((Duration duration) { SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
...@@ -702,6 +743,7 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke ...@@ -702,6 +743,7 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
} }
} }
}); });
widget.onReorderEnd?.call(_insertIndex!);
} }
void _dropCompleted() { void _dropCompleted() {
......
...@@ -1470,6 +1470,82 @@ void main() { ...@@ -1470,6 +1470,82 @@ void main() {
expect(items.take(8), orderedEquals(<int>[0, 1, 2, 3, 4, 5, 6, 7])); expect(items.take(8), orderedEquals(<int>[0, 1, 2, 3, 4, 5, 6, 7]));
}); });
testWidgets('ReorderableListView calls onReorderStart and onReorderEnd correctly', (WidgetTester tester) async {
final List<int> items = List<int>.generate(8, (int index) => index);
int? startIndex, endIndex;
final Finder item0 = find.textContaining('item 0');
void handleReorder(int fromIndex, int toIndex) {
if (toIndex > fromIndex) {
toIndex -= 1;
}
items.insert(toIndex, items.removeAt(fromIndex));
}
await tester.pumpWidget(MaterialApp(
home: ReorderableListView.builder(
itemBuilder: (BuildContext context, int index) {
return SizedBox(
key: ValueKey<int>(items[index]),
height: 100,
child: ReorderableDragStartListener(
index: index,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('item ${items[index]}'),
],
),
),
);
},
itemCount: items.length,
onReorder: handleReorder,
onReorderStart: (int index) {
startIndex = index;
},
onReorderEnd: (int index) {
endIndex = index;
},
),
));
TestGesture drag = await tester.startGesture(tester.getCenter(item0));
await tester.pump(kPressTimeout);
// Drag enough for move to start.
await drag.moveBy(const Offset(0, 20));
expect(startIndex, equals(0));
expect(endIndex, isNull);
// Move item0 from index 0 to index 3
await drag.moveBy(const Offset(0, 300));
await tester.pumpAndSettle();
await drag.up();
await tester.pumpAndSettle();
expect(endIndex, equals(3));
startIndex = null;
endIndex = null;
drag = await tester.startGesture(tester.getCenter(item0));
await tester.pump(kPressTimeout);
// Drag enough for move to start.
await drag.moveBy(const Offset(0, 20));
expect(startIndex, equals(2));
expect(endIndex, isNull);
// Move item0 from index 2 to index 0
await drag.moveBy(const Offset(0, -200));
await tester.pumpAndSettle();
await drag.up();
await tester.pumpAndSettle();
expect(endIndex, equals(0));
});
testWidgets('ReorderableListView throws an error when key is not passed to its children', (WidgetTester tester) async { testWidgets('ReorderableListView throws an error when key is not passed to its children', (WidgetTester tester) async {
final Widget reorderableListView = ReorderableListView.builder( final Widget reorderableListView = ReorderableListView.builder(
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
......
...@@ -467,6 +467,130 @@ void main() { ...@@ -467,6 +467,130 @@ void main() {
expect(tester.getTopLeft(find.text('item 0')), const Offset(0, 500)); expect(tester.getTopLeft(find.text('item 0')), const Offset(0, 500));
}); });
testWidgets('SliverReorderableList calls onReorderStart and onReorderEnd correctly', (WidgetTester tester) async {
final List<int> items = List<int>.generate(8, (int index) => index);
int? startIndex, endIndex;
final Finder item0 = find.textContaining('item 0');
await tester.pumpWidget(TestList(
items: items,
onReorderStart: (int index) {
startIndex = index;
},
onReorderEnd: (int index) {
endIndex = index;
},
));
TestGesture drag = await tester.startGesture(tester.getCenter(item0));
await tester.pump(kPressTimeout);
// Drag enough for move to start.
await drag.moveBy(const Offset(0, 20));
expect(startIndex, equals(0));
expect(endIndex, isNull);
// Move item0 from index 0 to index 3
await drag.moveBy(const Offset(0, 300));
await tester.pumpAndSettle();
await drag.up();
await tester.pumpAndSettle();
expect(endIndex, equals(3));
startIndex = null;
endIndex = null;
drag = await tester.startGesture(tester.getCenter(item0));
await tester.pump(kPressTimeout);
// Drag enough for move to start.
await drag.moveBy(const Offset(0, 20));
expect(startIndex, equals(2));
expect(endIndex, isNull);
// Move item0 from index 2 to index 0
await drag.moveBy(const Offset(0, -200));
await tester.pumpAndSettle();
await drag.up();
await tester.pumpAndSettle();
expect(endIndex, equals(0));
});
testWidgets('ReorderableList calls onReorderStart and onReorderEnd correctly', (WidgetTester tester) async {
final List<int> items = List<int>.generate(8, (int index) => index);
int? startIndex, endIndex;
final Finder item0 = find.textContaining('item 0');
void handleReorder(int fromIndex, int toIndex) {
if (toIndex > fromIndex) {
toIndex -= 1;
}
items.insert(toIndex, items.removeAt(fromIndex));
}
await tester.pumpWidget(MaterialApp(
home: ReorderableList(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return SizedBox(
key: ValueKey<int>(items[index]),
height: 100,
child: ReorderableDelayedDragStartListener(
index: index,
child: Text('item ${items[index]}'),
),
);
},
onReorder: handleReorder,
onReorderStart: (int index) {
startIndex = index;
},
onReorderEnd: (int index) {
endIndex = index;
},
),
));
TestGesture drag = await tester.startGesture(tester.getCenter(item0));
await tester.pump(kLongPressTimeout);
// Drag enough for move to start.
await drag.moveBy(const Offset(0, 20));
expect(startIndex, equals(0));
expect(endIndex, isNull);
// Move item0 from index 0 to index 3
await drag.moveBy(const Offset(0, 300));
await tester.pumpAndSettle();
await drag.up();
await tester.pumpAndSettle();
expect(endIndex, equals(3));
startIndex = null;
endIndex = null;
drag = await tester.startGesture(tester.getCenter(item0));
await tester.pump(kLongPressTimeout);
// Drag enough for move to start.
await drag.moveBy(const Offset(0, 20));
expect(startIndex, equals(2));
expect(endIndex, isNull);
// Move item0 from index 2 to index 0
await drag.moveBy(const Offset(0, -200));
await tester.pumpAndSettle();
await drag.up();
await tester.pumpAndSettle();
expect(endIndex, equals(0));
});
testWidgets('ReorderableList asserts on both non-null itemExtent and prototypeItem', (WidgetTester tester) async { testWidgets('ReorderableList asserts on both non-null itemExtent and prototypeItem', (WidgetTester tester) async {
final List<int> numbers = <int>[0,1,2]; final List<int> numbers = <int>[0,1,2];
expect(() => ReorderableList( expect(() => ReorderableList(
...@@ -794,6 +918,8 @@ class TestList extends StatefulWidget { ...@@ -794,6 +918,8 @@ class TestList extends StatefulWidget {
this.proxyDecorator, this.proxyDecorator,
required this.items, required this.items,
this.reverse = false, this.reverse = false,
this.onReorderStart,
this.onReorderEnd,
}) : super(key: key); }) : super(key: key);
final List<int> items; final List<int> items;
...@@ -801,6 +927,7 @@ class TestList extends StatefulWidget { ...@@ -801,6 +927,7 @@ class TestList extends StatefulWidget {
final Color? iconColor; final Color? iconColor;
final ReorderItemProxyDecorator? proxyDecorator; final ReorderItemProxyDecorator? proxyDecorator;
final bool reverse; final bool reverse;
final void Function(int)? onReorderStart, onReorderEnd;
@override @override
State<TestList> createState() => _TestListState(); State<TestList> createState() => _TestListState();
...@@ -849,6 +976,8 @@ class _TestListState extends State<TestList> { ...@@ -849,6 +976,8 @@ class _TestListState extends State<TestList> {
}); });
}, },
proxyDecorator: widget.proxyDecorator, proxyDecorator: widget.proxyDecorator,
onReorderStart: widget.onReorderStart,
onReorderEnd: widget.onReorderEnd,
), ),
], ],
); );
......
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