[ReorderableListView] Add footer
(#92086)
This commit is contained in:
parent
839bd6317f
commit
fd71de13fd
@ -72,6 +72,7 @@ class ReorderableListView extends StatefulWidget {
|
|||||||
this.buildDefaultDragHandles = true,
|
this.buildDefaultDragHandles = true,
|
||||||
this.padding,
|
this.padding,
|
||||||
this.header,
|
this.header,
|
||||||
|
this.footer,
|
||||||
this.scrollDirection = Axis.vertical,
|
this.scrollDirection = Axis.vertical,
|
||||||
this.reverse = false,
|
this.reverse = false,
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
@ -141,6 +142,7 @@ class ReorderableListView extends StatefulWidget {
|
|||||||
this.buildDefaultDragHandles = true,
|
this.buildDefaultDragHandles = true,
|
||||||
this.padding,
|
this.padding,
|
||||||
this.header,
|
this.header,
|
||||||
|
this.footer,
|
||||||
this.scrollDirection = Axis.vertical,
|
this.scrollDirection = Axis.vertical,
|
||||||
this.reverse = false,
|
this.reverse = false,
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
@ -214,6 +216,11 @@ class ReorderableListView extends StatefulWidget {
|
|||||||
/// If null, no header will appear before the list.
|
/// If null, no header will appear before the list.
|
||||||
final Widget? header;
|
final Widget? header;
|
||||||
|
|
||||||
|
/// A non-reorderable footer item to show after the items of the list.
|
||||||
|
///
|
||||||
|
/// If null, no footer will appear after the list.
|
||||||
|
final Widget? footer;
|
||||||
|
|
||||||
/// {@macro flutter.widgets.scroll_view.scrollDirection}
|
/// {@macro flutter.widgets.scroll_view.scrollDirection}
|
||||||
final Axis scrollDirection;
|
final Axis scrollDirection;
|
||||||
|
|
||||||
@ -426,37 +433,39 @@ class _ReorderableListViewState extends State<ReorderableListView> {
|
|||||||
assert(debugCheckHasMaterialLocalizations(context));
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
assert(debugCheckHasOverlay(context));
|
assert(debugCheckHasOverlay(context));
|
||||||
|
|
||||||
// If there is a header we can't just apply the padding to the list,
|
// If there is a header or footer we can't just apply the padding to the list,
|
||||||
// so we break it up into padding for the header and padding for the list.
|
// so we break it up into padding for the header, footer and padding for the list.
|
||||||
final EdgeInsets padding = widget.padding ?? EdgeInsets.zero;
|
final EdgeInsets padding = widget.padding ?? EdgeInsets.zero;
|
||||||
late final EdgeInsets headerPadding;
|
late final EdgeInsets headerPadding;
|
||||||
|
late final EdgeInsets footerPadding;
|
||||||
late final EdgeInsets listPadding;
|
late final EdgeInsets listPadding;
|
||||||
|
|
||||||
if (widget.header == null) {
|
if (widget.header == null && widget.footer == null) {
|
||||||
headerPadding = EdgeInsets.zero;
|
headerPadding = EdgeInsets.zero;
|
||||||
|
footerPadding = EdgeInsets.zero;
|
||||||
listPadding = padding;
|
listPadding = padding;
|
||||||
} else {
|
} else if (widget.header != null || widget.footer != null) {
|
||||||
switch (widget.scrollDirection) {
|
switch (widget.scrollDirection) {
|
||||||
case Axis.horizontal:
|
case Axis.horizontal:
|
||||||
if (widget.reverse) {
|
if (widget.reverse) {
|
||||||
// Header on the right
|
|
||||||
headerPadding = EdgeInsets.fromLTRB(0, padding.top, padding.right, padding.bottom);
|
headerPadding = EdgeInsets.fromLTRB(0, padding.top, padding.right, padding.bottom);
|
||||||
listPadding = EdgeInsets.fromLTRB(padding.left, padding.top, 0, padding.bottom);
|
listPadding = EdgeInsets.fromLTRB(widget.footer != null ? 0 : padding.left, padding.top, widget.header != null ? 0 : padding.right, padding.bottom);
|
||||||
|
footerPadding = EdgeInsets.fromLTRB(padding.left, padding.top, 0, padding.bottom);
|
||||||
} else {
|
} else {
|
||||||
// Header on the left
|
|
||||||
headerPadding = EdgeInsets.fromLTRB(padding.left, padding.top, 0, padding.bottom);
|
headerPadding = EdgeInsets.fromLTRB(padding.left, padding.top, 0, padding.bottom);
|
||||||
listPadding = EdgeInsets.fromLTRB(0, padding.top, padding.right, padding.bottom);
|
listPadding = EdgeInsets.fromLTRB(widget.header != null ? 0 : padding.left, padding.top, widget.footer != null ? 0 : padding.right, padding.bottom);
|
||||||
|
footerPadding = EdgeInsets.fromLTRB(0, padding.top, padding.right, padding.bottom);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Axis.vertical:
|
case Axis.vertical:
|
||||||
if (widget.reverse) {
|
if (widget.reverse) {
|
||||||
// Header on the bottom
|
|
||||||
headerPadding = EdgeInsets.fromLTRB(padding.left, 0, padding.right, padding.bottom);
|
headerPadding = EdgeInsets.fromLTRB(padding.left, 0, padding.right, padding.bottom);
|
||||||
listPadding = EdgeInsets.fromLTRB(padding.left, padding.top, padding.right, 0);
|
listPadding = EdgeInsets.fromLTRB(padding.left, widget.footer != null ? 0 : padding.top, padding.right, widget.header != null ? 0 : padding.bottom);
|
||||||
|
footerPadding = EdgeInsets.fromLTRB(padding.left, padding.top, padding.right, 0);
|
||||||
} else {
|
} else {
|
||||||
// Header on the top
|
|
||||||
headerPadding = EdgeInsets.fromLTRB(padding.left, padding.top, padding.right, 0);
|
headerPadding = EdgeInsets.fromLTRB(padding.left, padding.top, padding.right, 0);
|
||||||
listPadding = EdgeInsets.fromLTRB(padding.left, 0, padding.right, padding.bottom);
|
listPadding = EdgeInsets.fromLTRB(padding.left, widget.header != null ? 0 : padding.top, padding.right, widget.footer != null ? 0 : padding.bottom);
|
||||||
|
footerPadding = EdgeInsets.fromLTRB(padding.left, 0, padding.right, padding.bottom);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -494,6 +503,11 @@ class _ReorderableListViewState extends State<ReorderableListView> {
|
|||||||
proxyDecorator: widget.proxyDecorator ?? _proxyDecorator,
|
proxyDecorator: widget.proxyDecorator ?? _proxyDecorator,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (widget.footer != null)
|
||||||
|
SliverPadding(
|
||||||
|
padding: footerPadding,
|
||||||
|
sliver: SliverToBoxAdapter(child: widget.footer),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ void main() {
|
|||||||
|
|
||||||
Widget build({
|
Widget build({
|
||||||
Widget? header,
|
Widget? header,
|
||||||
|
Widget? footer,
|
||||||
Axis scrollDirection = Axis.vertical,
|
Axis scrollDirection = Axis.vertical,
|
||||||
bool reverse = false,
|
bool reverse = false,
|
||||||
EdgeInsets padding = EdgeInsets.zero,
|
EdgeInsets padding = EdgeInsets.zero,
|
||||||
@ -51,6 +52,7 @@ void main() {
|
|||||||
width: itemHeight * 10,
|
width: itemHeight * 10,
|
||||||
child: ReorderableListView(
|
child: ReorderableListView(
|
||||||
header: header,
|
header: header,
|
||||||
|
footer: footer,
|
||||||
scrollDirection: scrollDirection,
|
scrollDirection: scrollDirection,
|
||||||
onReorder: onReorder,
|
onReorder: onReorder,
|
||||||
reverse: reverse,
|
reverse: reverse,
|
||||||
@ -157,6 +159,20 @@ void main() {
|
|||||||
expect(listItems, orderedEquals(<String>['Item 2', 'Item 3', 'Item 4', 'Item 1']));
|
expect(listItems, orderedEquals(<String>['Item 2', 'Item 3', 'Item 4', 'Item 1']));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('properly reorders with a footer', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(build(footer: const Text('Footer Text')));
|
||||||
|
expect(find.text('Footer Text'), findsOneWidget);
|
||||||
|
expect(listItems, orderedEquals(originalListItems));
|
||||||
|
await longPressDrag(
|
||||||
|
tester,
|
||||||
|
tester.getCenter(find.text('Item 1')),
|
||||||
|
tester.getCenter(find.text('Item 4')) + const Offset(0.0, itemHeight * 2),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text('Footer Text'), findsOneWidget);
|
||||||
|
expect(listItems, orderedEquals(<String>['Item 2', 'Item 3', 'Item 4', 'Item 1']));
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('properly determines the vertical drop area extents', (WidgetTester tester) async {
|
testWidgets('properly determines the vertical drop area extents', (WidgetTester tester) async {
|
||||||
final Widget reorderableListView = ReorderableListView(
|
final Widget reorderableListView = ReorderableListView(
|
||||||
onReorder: (int oldIndex, int newIndex) { },
|
onReorder: (int oldIndex, int newIndex) { },
|
||||||
@ -764,6 +780,29 @@ void main() {
|
|||||||
expect(listItems, orderedEquals(<String>['Item 2', 'Item 4', 'Item 3', 'Item 1']));
|
expect(listItems, orderedEquals(<String>['Item 2', 'Item 4', 'Item 3', 'Item 1']));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('properly reorders with a footer', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(build(footer: const Text('Footer Text'), scrollDirection: Axis.horizontal));
|
||||||
|
expect(find.text('Footer Text'), findsOneWidget);
|
||||||
|
expect(listItems, orderedEquals(originalListItems));
|
||||||
|
await longPressDrag(
|
||||||
|
tester,
|
||||||
|
tester.getCenter(find.text('Item 1')),
|
||||||
|
tester.getCenter(find.text('Item 4')) + const Offset(itemHeight * 2, 0.0),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text('Footer Text'), findsOneWidget);
|
||||||
|
expect(listItems, orderedEquals(<String>['Item 2', 'Item 3', 'Item 4', 'Item 1']));
|
||||||
|
await tester.pumpWidget(build(footer: const Text('Footer Text'), scrollDirection: Axis.horizontal));
|
||||||
|
await longPressDrag(
|
||||||
|
tester,
|
||||||
|
tester.getCenter(find.text('Item 4')),
|
||||||
|
tester.getCenter(find.text('Item 3')),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text('Footer Text'), findsOneWidget);
|
||||||
|
expect(listItems, orderedEquals(<String>['Item 2', 'Item 4', 'Item 3', 'Item 1']));
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('properly determines the horizontal drop area extents', (WidgetTester tester) async {
|
testWidgets('properly determines the horizontal drop area extents', (WidgetTester tester) async {
|
||||||
final Widget reorderableListView = ReorderableListView(
|
final Widget reorderableListView = ReorderableListView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
@ -1242,7 +1281,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('Padding', () {
|
group('Padding', () {
|
||||||
testWidgets('Padding with no header', (WidgetTester tester) async {
|
testWidgets('Padding with no header & footer', (WidgetTester tester) async {
|
||||||
const EdgeInsets padding = EdgeInsets.fromLTRB(10, 20, 30, 40);
|
const EdgeInsets padding = EdgeInsets.fromLTRB(10, 20, 30, 40);
|
||||||
|
|
||||||
// Vertical
|
// Vertical
|
||||||
@ -1256,35 +1295,62 @@ void main() {
|
|||||||
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(154, 20, 202, 560));
|
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(154, 20, 202, 560));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Padding with header', (WidgetTester tester) async {
|
testWidgets('Padding with header or footer', (WidgetTester tester) async {
|
||||||
const EdgeInsets padding = EdgeInsets.fromLTRB(10, 20, 30, 40);
|
const EdgeInsets padding = EdgeInsets.fromLTRB(10, 20, 30, 40);
|
||||||
const Key headerKey = Key('Header');
|
const Key headerKey = Key('Header');
|
||||||
|
const Key footerKey = Key('Footer');
|
||||||
const Widget verticalHeader = SizedBox(key: headerKey, height: 10);
|
const Widget verticalHeader = SizedBox(key: headerKey, height: 10);
|
||||||
const Widget horizontalHeader = SizedBox(key: headerKey, width: 10);
|
const Widget horizontalHeader = SizedBox(key: headerKey, width: 10);
|
||||||
|
const Widget verticalFooter = SizedBox(key: footerKey, height: 10);
|
||||||
|
const Widget horizontalFooter = SizedBox(key: footerKey, width: 10);
|
||||||
|
|
||||||
// Vertical
|
// Vertical Header
|
||||||
await tester.pumpWidget(build(padding: padding, header: verticalHeader));
|
await tester.pumpWidget(build(padding: padding, header: verticalHeader));
|
||||||
expect(tester.getRect(find.byKey(headerKey)), const Rect.fromLTRB(10, 20, 770, 30));
|
expect(tester.getRect(find.byKey(headerKey)), const Rect.fromLTRB(10, 20, 770, 30));
|
||||||
expect(tester.getRect(find.byKey(const Key('Item 1'))), const Rect.fromLTRB(10, 30, 770, 78));
|
expect(tester.getRect(find.byKey(const Key('Item 1'))), const Rect.fromLTRB(10, 30, 770, 78));
|
||||||
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(10, 174, 770, 222));
|
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(10, 174, 770, 222));
|
||||||
|
|
||||||
// Vertical, reversed
|
// Vertical Footer
|
||||||
|
await tester.pumpWidget(build(padding: padding, footer: verticalFooter));
|
||||||
|
expect(tester.getRect(find.byKey(footerKey)), const Rect.fromLTRB(10, 212, 770, 222));
|
||||||
|
expect(tester.getRect(find.byKey(const Key('Item 1'))), const Rect.fromLTRB(10, 20, 770, 68));
|
||||||
|
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(10, 164, 770, 212));
|
||||||
|
|
||||||
|
// Vertical Header, reversed
|
||||||
await tester.pumpWidget(build(padding: padding, header: verticalHeader, reverse: true));
|
await tester.pumpWidget(build(padding: padding, header: verticalHeader, reverse: true));
|
||||||
expect(tester.getRect(find.byKey(headerKey)), const Rect.fromLTRB(10, 550, 770, 560));
|
expect(tester.getRect(find.byKey(headerKey)), const Rect.fromLTRB(10, 550, 770, 560));
|
||||||
expect(tester.getRect(find.byKey(const Key('Item 1'))), const Rect.fromLTRB(10, 502, 770, 550));
|
expect(tester.getRect(find.byKey(const Key('Item 1'))), const Rect.fromLTRB(10, 502, 770, 550));
|
||||||
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(10, 358, 770, 406));
|
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(10, 358, 770, 406));
|
||||||
|
|
||||||
// Horizontal
|
// Vertical Footer, reversed
|
||||||
|
await tester.pumpWidget(build(padding: padding, footer: verticalFooter, reverse: true));
|
||||||
|
expect(tester.getRect(find.byKey(footerKey)), const Rect.fromLTRB(10, 358, 770, 368));
|
||||||
|
expect(tester.getRect(find.byKey(const Key('Item 1'))), const Rect.fromLTRB(10, 512, 770, 560));
|
||||||
|
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(10, 368, 770, 416));
|
||||||
|
|
||||||
|
// Horizontal Header
|
||||||
await tester.pumpWidget(build(padding: padding, header: horizontalHeader, scrollDirection: Axis.horizontal));
|
await tester.pumpWidget(build(padding: padding, header: horizontalHeader, scrollDirection: Axis.horizontal));
|
||||||
expect(tester.getRect(find.byKey(headerKey)), const Rect.fromLTRB(10, 20, 20, 560));
|
expect(tester.getRect(find.byKey(headerKey)), const Rect.fromLTRB(10, 20, 20, 560));
|
||||||
expect(tester.getRect(find.byKey(const Key('Item 1'))), const Rect.fromLTRB(20, 20, 68, 560));
|
expect(tester.getRect(find.byKey(const Key('Item 1'))), const Rect.fromLTRB(20, 20, 68, 560));
|
||||||
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(164, 20, 212, 560));
|
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(164, 20, 212, 560));
|
||||||
|
|
||||||
// Horizontal, reversed
|
// // Horizontal Footer
|
||||||
|
await tester.pumpWidget(build(padding: padding, footer: horizontalFooter, scrollDirection: Axis.horizontal));
|
||||||
|
expect(tester.getRect(find.byKey(footerKey)), const Rect.fromLTRB(202, 20, 212, 560));
|
||||||
|
expect(tester.getRect(find.byKey(const Key('Item 1'))), const Rect.fromLTRB(10, 20, 58, 560));
|
||||||
|
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(154, 20, 202, 560));
|
||||||
|
|
||||||
|
// Horizontal Header, reversed
|
||||||
await tester.pumpWidget(build(padding: padding, header: horizontalHeader, scrollDirection: Axis.horizontal, reverse: true));
|
await tester.pumpWidget(build(padding: padding, header: horizontalHeader, scrollDirection: Axis.horizontal, reverse: true));
|
||||||
expect(tester.getRect(find.byKey(headerKey)), const Rect.fromLTRB(760, 20, 770, 560));
|
expect(tester.getRect(find.byKey(headerKey)), const Rect.fromLTRB(760, 20, 770, 560));
|
||||||
expect(tester.getRect(find.byKey(const Key('Item 1'))), const Rect.fromLTRB(712, 20, 760, 560));
|
expect(tester.getRect(find.byKey(const Key('Item 1'))), const Rect.fromLTRB(712, 20, 760, 560));
|
||||||
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(568, 20, 616, 560));
|
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(568, 20, 616, 560));
|
||||||
|
|
||||||
|
// // Horizontal Footer, reversed
|
||||||
|
await tester.pumpWidget(build(padding: padding, footer: horizontalFooter, scrollDirection: Axis.horizontal, reverse: true));
|
||||||
|
expect(tester.getRect(find.byKey(footerKey)), const Rect.fromLTRB(568, 20, 578, 560));
|
||||||
|
expect(tester.getRect(find.byKey(const Key('Item 1'))), const Rect.fromLTRB(722, 20, 770, 560));
|
||||||
|
expect(tester.getRect(find.byKey(const Key('Item 4'))), const Rect.fromLTRB(578, 20, 626, 560));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user