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