Unverified Commit f9905fc4 authored by Viren Khatri's avatar Viren Khatri Committed by GitHub

prototypeItem added to ReorderableList and ReorderableListView (#81604)

prototypeItem added to ReorderableList and ReorderableListView along with tests
parent 2d89ebb9
......@@ -77,6 +77,7 @@ class ReorderableListView extends StatefulWidget {
required List<Widget> children,
required this.onReorder,
this.itemExtent,
this.prototypeItem,
this.proxyDecorator,
this.buildDefaultDragHandles = true,
this.padding,
......@@ -96,6 +97,10 @@ class ReorderableListView extends StatefulWidget {
}) : assert(scrollDirection != null),
assert(onReorder != null),
assert(children != null),
assert(
itemExtent == null || prototypeItem == null,
'You can only pass itemExtent or prototypeItem, not both',
),
assert(
children.every((Widget w) => w.key != null),
'All children of this widget must have a key.',
......@@ -170,6 +175,7 @@ class ReorderableListView extends StatefulWidget {
required this.itemCount,
required this.onReorder,
this.itemExtent,
this.prototypeItem,
this.proxyDecorator,
this.buildDefaultDragHandles = true,
this.padding,
......@@ -189,6 +195,10 @@ class ReorderableListView extends StatefulWidget {
}) : assert(scrollDirection != null),
assert(itemCount >= 0),
assert(onReorder != null),
assert(
itemExtent == null || prototypeItem == null,
'You can only pass itemExtent or prototypeItem, not both',
),
assert(buildDefaultDragHandles != null),
super(key: key);
......@@ -328,6 +338,9 @@ class ReorderableListView extends StatefulWidget {
/// {@macro flutter.widgets.list_view.itemExtent}
final double? itemExtent;
/// {@macro flutter.widgets.list_view.prototypeItem}
final Widget? prototypeItem;
@override
_ReorderableListViewState createState() => _ReorderableListViewState();
}
......@@ -551,6 +564,7 @@ class _ReorderableListViewState extends State<ReorderableListView> {
sliver: SliverReorderableList(
itemBuilder: _itemBuilder,
itemExtent: widget.itemExtent,
prototypeItem: widget.prototypeItem,
itemCount: widget.itemCount,
onReorder: widget.onReorder,
proxyDecorator: widget.proxyDecorator ?? _proxyDecorator,
......
......@@ -18,6 +18,7 @@ import 'scroll_position.dart';
import 'scroll_view.dart';
import 'scrollable.dart';
import 'sliver.dart';
import 'sliver_prototype_extent_list.dart';
import 'ticker_provider.dart';
import 'transitions.dart';
......@@ -113,6 +114,7 @@ class ReorderableList extends StatefulWidget {
required this.itemCount,
required this.onReorder,
this.itemExtent,
this.prototypeItem,
this.proxyDecorator,
this.padding,
this.scrollDirection = Axis.vertical,
......@@ -128,6 +130,10 @@ class ReorderableList extends StatefulWidget {
this.restorationId,
this.clipBehavior = Clip.hardEdge,
}) : assert(itemCount >= 0),
assert(
itemExtent == null || prototypeItem == null,
'You can only pass itemExtent or prototypeItem, not both',
),
super(key: key);
/// {@template flutter.widgets.reorderable_list.itemBuilder}
......@@ -215,6 +221,9 @@ class ReorderableList extends StatefulWidget {
/// {@macro flutter.widgets.list_view.itemExtent}
final double? itemExtent;
/// {@macro flutter.widgets.list_view.prototypeItem}
final Widget? prototypeItem;
/// The state from the closest instance of this class that encloses the given
/// context.
///
......@@ -342,6 +351,7 @@ class ReorderableListState extends State<ReorderableList> {
sliver: SliverReorderableList(
key: _sliverReorderableListKey,
itemExtent: widget.itemExtent,
prototypeItem: widget.prototypeItem,
itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount,
onReorder: widget.onReorder,
......@@ -386,8 +396,13 @@ class SliverReorderableList extends StatefulWidget {
required this.itemCount,
required this.onReorder,
this.itemExtent,
this.prototypeItem,
this.proxyDecorator,
}) : assert(itemCount >= 0),
assert(
itemExtent == null || prototypeItem == null,
'You can only pass itemExtent or prototypeItem, not both',
),
super(key: key);
/// {@macro flutter.widgets.reorderable_list.itemBuilder}
......@@ -405,6 +420,9 @@ class SliverReorderableList extends StatefulWidget {
/// {@macro flutter.widgets.list_view.itemExtent}
final double? itemExtent;
/// {@macro flutter.widgets.list_view.prototypeItem}
final Widget? prototypeItem;
@override
SliverReorderableListState createState() => SliverReorderableListState();
......@@ -841,12 +859,18 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
// list extent stable we add a dummy entry to the end.
childCount: widget.itemCount + (_dragInfo != null ? 1 : 0),
);
return widget.itemExtent != null
? SliverFixedExtentList(
itemExtent: widget.itemExtent!,
delegate: childrenDelegate,
)
: SliverList(delegate: childrenDelegate);
if (widget.itemExtent != null) {
return SliverFixedExtentList(
delegate: childrenDelegate,
itemExtent: widget.itemExtent!,
);
} else if (widget.prototypeItem != null) {
return SliverPrototypeExtentList(
delegate: childrenDelegate,
prototypeItem: widget.prototypeItem!,
);
}
return SliverList(delegate: childrenDelegate);
}
}
......
......@@ -1490,11 +1490,12 @@ class ListView extends BoxScrollView {
/// * [SliverFixedExtentList], the sliver used internally when this property
/// is provided. It constrains its box children to have a specific given
/// extent along the main axis.
/// {@endtemplate}
/// * The [prototypeItem] property, which allows forcing the children's
/// extent to be the same as the given widget.
/// {@endtemplate}
final double? itemExtent;
/// {@template flutter.widgets.list_view.prototypeItem}
/// If non-null, forces the children to have the same extent as the given
/// widget in the scroll direction.
///
......@@ -1510,6 +1511,7 @@ class ListView extends BoxScrollView {
/// extent as a prototype item along the main axis.
/// * The [itemExtent] property, which allows forcing the children's extent
/// to a given value.
/// {@endtemplate}
final Widget? prototypeItem;
/// A delegate that provides the children for the [ListView].
......
......@@ -1506,6 +1506,35 @@ void main() {
expect(exception.toString(), contains('ReorderableListView widgets require an Overlay widget ancestor'));
});
testWidgets('ReorderableListView asserts on both non-null itemExtent and prototypeItem', (WidgetTester tester) async {
expect(() => ReorderableListView(
children: const <Widget>[],
itemExtent: 30,
prototypeItem: const SizedBox(),
onReorder: (int fromIndex, int toIndex) { },
), throwsAssertionError);
});
testWidgets('ReorderableListView.builder asserts on both non-null itemExtent and prototypeItem', (WidgetTester tester) async {
final List<int> numbers = <int>[0,1,2];
expect(() => ReorderableListView.builder(
itemBuilder: (BuildContext context, int index) {
return SizedBox(
key: ValueKey<int>(numbers[index]),
height: 20 + numbers[index] * 10,
child: ReorderableDragStartListener(
index: index,
child: Text(numbers[index].toString()),
)
);
},
itemCount: numbers.length,
itemExtent: 30,
prototypeItem: const SizedBox(),
onReorder: (int fromIndex, int toIndex) { },
), throwsAssertionError);
});
testWidgets('if itemExtent is non-null, children have same extent in the scroll direction', (WidgetTester tester) async {
final List<int> numbers = <int>[0,1,2];
......@@ -1528,13 +1557,49 @@ void main() {
},
itemCount: numbers.length,
itemExtent: 30,
onReorder: (int fromIndex, int toIndex) {
if (fromIndex < toIndex) {
toIndex--;
}
final int value = numbers.removeAt(fromIndex);
numbers.insert(toIndex, value);
onReorder: (int fromIndex, int toIndex) { },
);
},
),
),
)
);
final double item0Height = tester.getSize(find.text('0').hitTestable()).height;
final double item1Height = tester.getSize(find.text('1').hitTestable()).height;
final double item2Height = tester.getSize(find.text('2').hitTestable()).height;
expect(item0Height, 30.0);
expect(item1Height, 30.0);
expect(item2Height, 30.0);
});
testWidgets('if prototypeItem is non-null, children have same extent in the scroll direction', (WidgetTester tester) async {
final List<int> numbers = <int>[0,1,2];
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return ReorderableListView.builder(
itemBuilder: (BuildContext context, int index) {
return SizedBox(
key: ValueKey<int>(numbers[index]),
// children with different heights
height: 20 + numbers[index] * 10,
child: ReorderableDragStartListener(
index: index,
child: Text(numbers[index].toString()),
)
);
},
itemCount: numbers.length,
prototypeItem: const SizedBox(
height: 30,
child: Text('3'),
),
onReorder: (int oldIndex, int newIndex) { },
);
},
),
......
......@@ -268,6 +268,46 @@ void main() {
expect(getItemFadeTransition(), findsNothing);
});
testWidgets('ReorderableList asserts on both non-null itemExtent and prototypeItem', (WidgetTester tester) async {
final List<int> numbers = <int>[0,1,2];
expect(() => ReorderableList(
itemBuilder: (BuildContext context, int index) {
return SizedBox(
key: ValueKey<int>(numbers[index]),
height: 20 + numbers[index] * 10,
child: ReorderableDragStartListener(
index: index,
child: Text(numbers[index].toString()),
)
);
},
itemCount: numbers.length,
itemExtent: 30,
prototypeItem: const SizedBox(),
onReorder: (int fromIndex, int toIndex) { },
), throwsAssertionError);
});
testWidgets('SliverReorderableList asserts on both non-null itemExtent and prototypeItem', (WidgetTester tester) async {
final List<int> numbers = <int>[0,1,2];
expect(() => SliverReorderableList(
itemBuilder: (BuildContext context, int index) {
return SizedBox(
key: ValueKey<int>(numbers[index]),
height: 20 + numbers[index] * 10,
child: ReorderableDragStartListener(
index: index,
child: Text(numbers[index].toString()),
)
);
},
itemCount: numbers.length,
itemExtent: 30,
prototypeItem: const SizedBox(),
onReorder: (int fromIndex, int toIndex) { },
), throwsAssertionError);
});
testWidgets('if itemExtent is non-null, children have same extent in the scroll direction', (WidgetTester tester) async {
final List<int> numbers = <int>[0,1,2];
......@@ -312,6 +352,48 @@ void main() {
expect(item1Height, 30.0);
expect(item2Height, 30.0);
});
testWidgets('if prototypeItem is non-null, children have same extent in the scroll direction', (WidgetTester tester) async {
final List<int> numbers = <int>[0,1,2];
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return ReorderableList(
itemBuilder: (BuildContext context, int index) {
return SizedBox(
key: ValueKey<int>(numbers[index]),
// children with different heights
height: 20 + numbers[index] * 10,
child: ReorderableDragStartListener(
index: index,
child: Text(numbers[index].toString()),
)
);
},
itemCount: numbers.length,
prototypeItem: const SizedBox(
height: 30,
child: Text('3'),
),
onReorder: (int oldIndex, int newIndex) { },
);
},
),
),
)
);
final double item0Height = tester.getSize(find.text('0').hitTestable()).height;
final double item1Height = tester.getSize(find.text('1').hitTestable()).height;
final double item2Height = tester.getSize(find.text('2').hitTestable()).height;
expect(item0Height, 30.0);
expect(item1Height, 30.0);
expect(item2Height, 30.0);
});
}
class TestList extends StatefulWidget {
......
......@@ -1233,7 +1233,7 @@ void main() {
expect(finder, findsOneWidget);
});
testWidgets('ListView asserts on both non-null itemExtent and prototypeItem', (WidgetTester tester) async {
testWidgets('ListView asserts on both non-null itemExtent and prototypeItem', (WidgetTester tester) async {
expect(() => ListView(
itemExtent: 100,
prototypeItem: const SizedBox(),
......@@ -1372,4 +1372,45 @@ void main() {
expect(item1Height, 30.0);
expect(item2Height, 30.0);
});
testWidgets('if prototypeItem is non-null, children have same extent in the scroll direction', (WidgetTester tester) async {
final List<int> numbers = <int>[0,1,2];
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return SizedBox(
key: ValueKey<int>(numbers[index]),
// children with different heights
height: 20 + numbers[index] * 10,
child: ReorderableDragStartListener(
index: index,
child: Text(numbers[index].toString()),
)
);
},
itemCount: numbers.length,
prototypeItem: const SizedBox(
height: 30,
child: Text('3'),
),
);
},
),
),
)
);
final double item0Height = tester.getSize(find.text('0').hitTestable()).height;
final double item1Height = tester.getSize(find.text('1').hitTestable()).height;
final double item2Height = tester.getSize(find.text('2').hitTestable()).height;
expect(item0Height, 30.0);
expect(item1Height, 30.0);
expect(item2Height, 30.0);
});
}
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