Unverified Commit 994133c7 authored by Darren Austin's avatar Darren Austin Committed by GitHub

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.
parent a2a520cb
......@@ -289,115 +289,7 @@ class ReorderableListView extends StatefulWidget {
_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> {
// 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) {
void reorder(int startIndex, int endIndex) {
if (startIndex != endIndex)
......@@ -530,6 +422,9 @@ class _ReorderableListContentState extends State<_ReorderableListContent> {
@override
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,
// 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
......
......@@ -212,7 +212,8 @@ void main() {
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(
children: <Widget>[
Container(
......@@ -234,23 +235,45 @@ void main() {
color: Colors.green,
),
],
scrollDirection: Axis.vertical,
onReorder: (int oldIndex, int newIndex) { },
);
await tester.pumpWidget(MaterialApp(
home: SizedBox(
home: Container(
color: Colors.white,
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);
// Drag it up to be partially over the top item.
await drag.moveBy(const Offset(0, -itemHeight / 3));
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(
find.byKey(const Key('blue')),
find.byType(ReorderableListView),
matchesGoldenFile('reorderable_list_test.vertical.drop_area.png'),
);
debugDisableShadows = true;
});
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) { },
);
final Widget overlay = Overlay(
initialEntries: <OverlayEntry>[
OverlayEntry(builder: (BuildContext context) => reorderableList)
],
);
final Widget boilerplate = Localizations(
locale: const Locale('en'),
delegates: const <LocalizationsDelegate<dynamic>>[
......@@ -443,7 +471,7 @@ void main() {
height: 100.0,
child: Directionality(
textDirection: TextDirection.ltr,
child: reorderableList,
child: overlay,
),
),
);
......@@ -795,7 +823,8 @@ void main() {
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(
children: <Widget>[
Container(
......@@ -821,19 +850,42 @@ void main() {
onReorder: (int oldIndex, int newIndex) { },
);
await tester.pumpWidget(MaterialApp(
home: SizedBox(
home: Container(
color: Colors.white,
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);
// Drag it left to be partially over the first item.
await drag.moveBy(const Offset(-itemHeight / 3, 0));
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(
find.byKey(const Key('blue')),
find.byType(ReorderableListView),
matchesGoldenFile('reorderable_list_test.horizontal.drop_area.png'),
);
debugDisableShadows = true;
});
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.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 {
......
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