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();
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user