Adds onReorderStart
and onReorderEnd
arguments to ReorderableList
. (#96049)
This commit is contained in:
parent
3e4a7c0d0d
commit
922bbfeed7
@ -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 {
|
|||||||
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 {
|
|||||||
/// {@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> {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
/// {@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> {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
/// {@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
|
|||||||
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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
widget.onReorderEnd?.call(_insertIndex!);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _dropCompleted() {
|
void _dropCompleted() {
|
||||||
|
@ -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() {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
proxyDecorator: widget.proxyDecorator,
|
proxyDecorator: widget.proxyDecorator,
|
||||||
|
onReorderStart: widget.onReorderStart,
|
||||||
|
onReorderEnd: widget.onReorderEnd,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user