Removed the built-in overlay from ReorderableListView. (#79024)
Removed the built-in overlay from the ReorderableListView so that it now depends on the surrounding overlay.
This commit is contained in:
parent
a2a520cba3
commit
994133c75c
@ -289,115 +289,7 @@ class ReorderableListView extends StatefulWidget {
|
|||||||
_ReorderableListViewState createState() => _ReorderableListViewState();
|
_ReorderableListViewState createState() => _ReorderableListViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This top-level state manages an Overlay that contains the list and
|
|
||||||
// also any items being dragged on top fo the list.
|
|
||||||
//
|
|
||||||
// The Overlay doesn't properly keep state by building new overlay entries,
|
|
||||||
// and so we cache a single OverlayEntry for use as the list layer.
|
|
||||||
// That overlay entry then builds a _ReorderableListContent which may
|
|
||||||
// insert items being dragged into the Overlay above itself.
|
|
||||||
class _ReorderableListViewState extends State<ReorderableListView> {
|
class _ReorderableListViewState extends State<ReorderableListView> {
|
||||||
// This entry contains the scrolling list itself.
|
|
||||||
late OverlayEntry _listOverlayEntry;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_listOverlayEntry = OverlayEntry(
|
|
||||||
opaque: true,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return _ReorderableListContent(
|
|
||||||
itemBuilder: widget.itemBuilder,
|
|
||||||
itemCount: widget.itemCount,
|
|
||||||
onReorder: widget.onReorder,
|
|
||||||
proxyDecorator: widget.proxyDecorator,
|
|
||||||
buildDefaultDragHandles: widget.buildDefaultDragHandles,
|
|
||||||
padding: widget.padding,
|
|
||||||
header: widget.header,
|
|
||||||
scrollDirection: widget.scrollDirection,
|
|
||||||
reverse: widget.reverse,
|
|
||||||
scrollController: widget.scrollController,
|
|
||||||
primary: widget.primary,
|
|
||||||
physics: widget.physics,
|
|
||||||
shrinkWrap: widget.shrinkWrap,
|
|
||||||
anchor: widget.anchor,
|
|
||||||
cacheExtent: widget.cacheExtent,
|
|
||||||
dragStartBehavior: widget.dragStartBehavior,
|
|
||||||
keyboardDismissBehavior: widget.keyboardDismissBehavior,
|
|
||||||
restorationId: widget.restorationId,
|
|
||||||
clipBehavior: widget.clipBehavior,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(ReorderableListView oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
// As this depends on pretty much everything, it
|
|
||||||
// is ok to mark this as dirty unconditionally.
|
|
||||||
_listOverlayEntry.markNeedsBuild();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
assert(debugCheckHasMaterialLocalizations(context));
|
|
||||||
return Overlay(
|
|
||||||
initialEntries: <OverlayEntry>[
|
|
||||||
_listOverlayEntry
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ReorderableListContent extends StatefulWidget {
|
|
||||||
const _ReorderableListContent({
|
|
||||||
required this.itemBuilder,
|
|
||||||
required this.itemCount,
|
|
||||||
required this.onReorder,
|
|
||||||
required this.proxyDecorator,
|
|
||||||
required this.buildDefaultDragHandles,
|
|
||||||
required this.padding,
|
|
||||||
required this.header,
|
|
||||||
required this.scrollDirection,
|
|
||||||
required this.reverse,
|
|
||||||
required this.scrollController,
|
|
||||||
required this.primary,
|
|
||||||
required this.physics,
|
|
||||||
required this.shrinkWrap,
|
|
||||||
required this.anchor,
|
|
||||||
required this.cacheExtent,
|
|
||||||
required this.dragStartBehavior,
|
|
||||||
required this.keyboardDismissBehavior,
|
|
||||||
required this.restorationId,
|
|
||||||
required this.clipBehavior,
|
|
||||||
});
|
|
||||||
|
|
||||||
final IndexedWidgetBuilder itemBuilder;
|
|
||||||
final int itemCount;
|
|
||||||
final ReorderCallback onReorder;
|
|
||||||
final ReorderItemProxyDecorator? proxyDecorator;
|
|
||||||
final bool buildDefaultDragHandles;
|
|
||||||
final EdgeInsets? padding;
|
|
||||||
final Widget? header;
|
|
||||||
final Axis scrollDirection;
|
|
||||||
final bool reverse;
|
|
||||||
final ScrollController? scrollController;
|
|
||||||
final bool? primary;
|
|
||||||
final ScrollPhysics? physics;
|
|
||||||
final bool shrinkWrap;
|
|
||||||
final double anchor;
|
|
||||||
final double? cacheExtent;
|
|
||||||
final DragStartBehavior dragStartBehavior;
|
|
||||||
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
|
|
||||||
final String? restorationId;
|
|
||||||
final Clip clipBehavior;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_ReorderableListContentState createState() => _ReorderableListContentState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ReorderableListContentState extends State<_ReorderableListContent> {
|
|
||||||
Widget _wrapWithSemantics(Widget child, int index) {
|
Widget _wrapWithSemantics(Widget child, int index) {
|
||||||
void reorder(int startIndex, int endIndex) {
|
void reorder(int startIndex, int endIndex) {
|
||||||
if (startIndex != endIndex)
|
if (startIndex != endIndex)
|
||||||
@ -530,6 +422,9 @@ class _ReorderableListContentState extends State<_ReorderableListContent> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
|
assert(debugCheckHasOverlay(context));
|
||||||
|
|
||||||
// If there is a header we can't just apply the padding to the list,
|
// If there is a header we can't just apply the padding to the list,
|
||||||
// so we wrap the CustomScrollView in the padding for the top, left and right
|
// so we wrap the CustomScrollView in the padding for the top, left and right
|
||||||
// and only add the padding from the bottom to the sliver list (or the equivalent
|
// and only add the padding from the bottom to the sliver list (or the equivalent
|
||||||
|
@ -212,7 +212,8 @@ void main() {
|
|||||||
expect(getListHeight(), kDraggingListHeight);
|
expect(getListHeight(), kDraggingListHeight);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Vertical drop area golden', (WidgetTester tester) async {
|
testWidgets('Vertical drag in progress golden image', (WidgetTester tester) async {
|
||||||
|
debugDisableShadows = false;
|
||||||
final Widget reorderableListView = ReorderableListView(
|
final Widget reorderableListView = ReorderableListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
@ -234,23 +235,45 @@ void main() {
|
|||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
scrollDirection: Axis.vertical,
|
|
||||||
onReorder: (int oldIndex, int newIndex) { },
|
onReorder: (int oldIndex, int newIndex) { },
|
||||||
);
|
);
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
home: SizedBox(
|
home: Container(
|
||||||
|
color: Colors.white,
|
||||||
height: itemHeight * 3,
|
height: itemHeight * 3,
|
||||||
child: reorderableListView,
|
// Wrap in an overlay so that the golden image includes the dragged item.
|
||||||
|
child: Overlay(
|
||||||
|
initialEntries: <OverlayEntry>[
|
||||||
|
OverlayEntry(builder: (BuildContext context) {
|
||||||
|
// Wrap the list in padding to test that the positioning
|
||||||
|
// is correct when the origin of the overlay is different
|
||||||
|
// from the list.
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: reorderableListView,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
await tester.startGesture(tester.getCenter(find.byKey(const Key('blue'))));
|
// Start dragging the second item.
|
||||||
|
final TestGesture drag = await tester.startGesture(tester.getCenter(find.byKey(const Key('blue'))));
|
||||||
await tester.pump(kLongPressTimeout + kPressTimeout);
|
await tester.pump(kLongPressTimeout + kPressTimeout);
|
||||||
|
|
||||||
|
// Drag it up to be partially over the top item.
|
||||||
|
await drag.moveBy(const Offset(0, -itemHeight / 3));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Should be an image of the second item overlapping the bottom of the
|
||||||
|
// first with a gap between the first and third and a drop shadow on
|
||||||
|
// the dragged item.
|
||||||
await expectLater(
|
await expectLater(
|
||||||
find.byKey(const Key('blue')),
|
find.byType(ReorderableListView),
|
||||||
matchesGoldenFile('reorderable_list_test.vertical.drop_area.png'),
|
matchesGoldenFile('reorderable_list_test.vertical.drop_area.png'),
|
||||||
);
|
);
|
||||||
|
debugDisableShadows = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Preserves children states when the list parent changes the order', (WidgetTester tester) async {
|
testWidgets('Preserves children states when the list parent changes the order', (WidgetTester tester) async {
|
||||||
@ -432,6 +455,11 @@ void main() {
|
|||||||
],
|
],
|
||||||
onReorder: (int oldIndex, int newIndex) { },
|
onReorder: (int oldIndex, int newIndex) { },
|
||||||
);
|
);
|
||||||
|
final Widget overlay = Overlay(
|
||||||
|
initialEntries: <OverlayEntry>[
|
||||||
|
OverlayEntry(builder: (BuildContext context) => reorderableList)
|
||||||
|
],
|
||||||
|
);
|
||||||
final Widget boilerplate = Localizations(
|
final Widget boilerplate = Localizations(
|
||||||
locale: const Locale('en'),
|
locale: const Locale('en'),
|
||||||
delegates: const <LocalizationsDelegate<dynamic>>[
|
delegates: const <LocalizationsDelegate<dynamic>>[
|
||||||
@ -443,7 +471,7 @@ void main() {
|
|||||||
height: 100.0,
|
height: 100.0,
|
||||||
child: Directionality(
|
child: Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: reorderableList,
|
child: overlay,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -795,7 +823,8 @@ void main() {
|
|||||||
expect(getListWidth(), kDraggingListWidth);
|
expect(getListWidth(), kDraggingListWidth);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Horizontal drop area golden', (WidgetTester tester) async {
|
testWidgets('Horizontal drag in progress golden image', (WidgetTester tester) async {
|
||||||
|
debugDisableShadows = false;
|
||||||
final Widget reorderableListView = ReorderableListView(
|
final Widget reorderableListView = ReorderableListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
@ -821,19 +850,42 @@ void main() {
|
|||||||
onReorder: (int oldIndex, int newIndex) { },
|
onReorder: (int oldIndex, int newIndex) { },
|
||||||
);
|
);
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
home: SizedBox(
|
home: Container(
|
||||||
|
color: Colors.white,
|
||||||
width: itemHeight * 3,
|
width: itemHeight * 3,
|
||||||
child: reorderableListView,
|
// Wrap in an overlay so that the golden image includes the dragged item.
|
||||||
|
child: Overlay(
|
||||||
|
initialEntries: <OverlayEntry>[
|
||||||
|
OverlayEntry(builder: (BuildContext context) {
|
||||||
|
// Wrap the list in padding to test that the positioning
|
||||||
|
// is correct when the origin of the overlay is different
|
||||||
|
// from the list.
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: reorderableListView,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
await tester.startGesture(tester.getCenter(find.byKey(const Key('blue'))));
|
// Start dragging the second item.
|
||||||
|
final TestGesture drag = await tester.startGesture(tester.getCenter(find.byKey(const Key('blue'))));
|
||||||
await tester.pump(kLongPressTimeout + kPressTimeout);
|
await tester.pump(kLongPressTimeout + kPressTimeout);
|
||||||
|
|
||||||
|
// Drag it left to be partially over the first item.
|
||||||
|
await drag.moveBy(const Offset(-itemHeight / 3, 0));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Should be an image of the second item overlapping the right of the
|
||||||
|
// first with a gap between the first and third and a drop shadow on
|
||||||
|
// the dragged item.
|
||||||
await expectLater(
|
await expectLater(
|
||||||
find.byKey(const Key('blue')),
|
find.byType(ReorderableListView),
|
||||||
matchesGoldenFile('reorderable_list_test.horizontal.drop_area.png'),
|
matchesGoldenFile('reorderable_list_test.horizontal.drop_area.png'),
|
||||||
);
|
);
|
||||||
|
debugDisableShadows = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Preserves children states when the list parent changes the order', (WidgetTester tester) async {
|
testWidgets('Preserves children states when the list parent changes the order', (WidgetTester tester) async {
|
||||||
@ -1342,6 +1394,38 @@ void main() {
|
|||||||
expect(exception, isFlutterError);
|
expect(exception, isFlutterError);
|
||||||
expect(exception.toString(), contains('Every item of ReorderableListView must have a key.'));
|
expect(exception.toString(), contains('Every item of ReorderableListView must have a key.'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Throws an error if no overlay present', (WidgetTester tester) async {
|
||||||
|
final Widget reorderableList = ReorderableListView(
|
||||||
|
children: const <Widget>[
|
||||||
|
SizedBox(width: 100.0, height: 100.0, child: Text('C'), key: Key('C')),
|
||||||
|
SizedBox(width: 100.0, height: 100.0, child: Text('B'), key: Key('B')),
|
||||||
|
SizedBox(width: 100.0, height: 100.0, child: Text('A'), key: Key('A')),
|
||||||
|
],
|
||||||
|
onReorder: (int oldIndex, int newIndex) { },
|
||||||
|
);
|
||||||
|
final Widget boilerplate = Localizations(
|
||||||
|
locale: const Locale('en'),
|
||||||
|
delegates: const <LocalizationsDelegate<dynamic>>[
|
||||||
|
DefaultMaterialLocalizations.delegate,
|
||||||
|
DefaultWidgetsLocalizations.delegate,
|
||||||
|
],
|
||||||
|
child:SizedBox(
|
||||||
|
width: 100.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: reorderableList,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(boilerplate);
|
||||||
|
|
||||||
|
final dynamic exception = tester.takeException();
|
||||||
|
expect(exception, isFlutterError);
|
||||||
|
expect(exception.toString(), contains('No Overlay widget found'));
|
||||||
|
expect(exception.toString(), contains('ReorderableListView widgets require an Overlay widget ancestor'));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> longPressDrag(WidgetTester tester, Offset start, Offset end) async {
|
Future<void> longPressDrag(WidgetTester tester, Offset start, Offset end) async {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user