Added "insertAll" and "removeAll" methods to AnimatedList (#115545)
* Added "insertAll" and "removeAll" method to AnimatedList * Fixed doc * Changes in documentation asked by reviewwer * Removed unnecessary asserts. * Doc changes asked by reviewer. * Doc changes. --------- Co-authored-by: Rashid Khabeer <rkhabeer84@gmail.com>
This commit is contained in:
@@ -128,6 +128,10 @@ class AnimatedList extends _AnimatedScrollView {
|
||||
/// animation is passed to [AnimatedList.itemBuilder] whenever the item's widget
|
||||
/// is needed.
|
||||
///
|
||||
/// When multiple items are inserted with [insertAllItems] an animation begins running.
|
||||
/// The animation is passed to [AnimatedList.itemBuilder] whenever the item's widget
|
||||
/// is needed.
|
||||
///
|
||||
/// When an item is removed with [removeItem] its animation is reversed.
|
||||
/// The removed item's animation is passed to the [removeItem] builder
|
||||
/// parameter.
|
||||
@@ -486,6 +490,13 @@ abstract class _AnimatedScrollViewState<T extends _AnimatedScrollView> extends S
|
||||
_sliverAnimatedMultiBoxKey.currentState!.insertItem(index, duration: duration);
|
||||
}
|
||||
|
||||
/// Insert multiple items at [index] and start an animation that will be passed
|
||||
/// to [AnimatedGrid.itemBuilder] or [AnimatedList.itemBuilder] when the items
|
||||
/// are visible.
|
||||
void insertAllItems(int index, int length, { Duration duration = _kDuration, bool isAsync = false }) {
|
||||
_sliverAnimatedMultiBoxKey.currentState!.insertAllItems(index, length, duration: duration);
|
||||
}
|
||||
|
||||
/// Remove the item at `index` and start an animation that will be passed to
|
||||
/// `builder` when the item is visible.
|
||||
///
|
||||
@@ -506,6 +517,19 @@ abstract class _AnimatedScrollViewState<T extends _AnimatedScrollView> extends S
|
||||
_sliverAnimatedMultiBoxKey.currentState!.removeItem(index, builder, duration: duration);
|
||||
}
|
||||
|
||||
/// Remove all the items and start an animation that will be passed to
|
||||
/// `builder` when the items are visible.
|
||||
///
|
||||
/// Items are removed immediately. However, the
|
||||
/// items will still appear for `duration`, and during that time
|
||||
/// `builder` must construct its widget as needed.
|
||||
///
|
||||
/// This method's semantics are the same as Dart's [List.clear] method: it
|
||||
/// removes all the items in the list.
|
||||
void removeAllItems(AnimatedRemovedItemBuilder builder, { Duration duration = _kDuration }) {
|
||||
_sliverAnimatedMultiBoxKey.currentState!.removeAllItems(builder, duration: duration);
|
||||
}
|
||||
|
||||
Widget _wrap(Widget sliver) {
|
||||
return CustomScrollView(
|
||||
scrollDirection: widget.scrollDirection,
|
||||
@@ -1046,6 +1070,15 @@ abstract class _SliverAnimatedMultiBoxAdaptorState<T extends _SliverAnimatedMult
|
||||
});
|
||||
}
|
||||
|
||||
/// Insert multiple items at [index] and start an animation that will be passed
|
||||
/// to [AnimatedGrid.itemBuilder] or [AnimatedList.itemBuilder] when the items
|
||||
/// are visible.
|
||||
void insertAllItems(int index, int length, { Duration duration = _kDuration }) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
insertItem(index + i, duration: duration);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the item at [index] and start an animation that will be passed
|
||||
/// to [builder] when the item is visible.
|
||||
///
|
||||
@@ -1094,4 +1127,19 @@ abstract class _SliverAnimatedMultiBoxAdaptorState<T extends _SliverAnimatedMult
|
||||
setState(() => _itemsCount -= 1);
|
||||
});
|
||||
}
|
||||
|
||||
/// Remove all the items and start an animation that will be passed to
|
||||
/// `builder` when the items are visible.
|
||||
///
|
||||
/// Items are removed immediately. However, the
|
||||
/// items will still appear for `duration` and during that time
|
||||
/// `builder` must construct its widget as needed.
|
||||
///
|
||||
/// This method's semantics are the same as Dart's [List.clear] method: it
|
||||
/// removes all the items in the list.
|
||||
void removeAllItems(AnimatedRemovedItemBuilder builder, { Duration duration = _kDuration }) {
|
||||
for(int i = _itemsCount - 1 ; i >= 0; i--) {
|
||||
removeItem(i, builder, duration: duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,32 @@ void main() {
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('removing item'), findsNothing);
|
||||
|
||||
listKey.currentState!.insertAllItems(0, 2);
|
||||
await tester.pump();
|
||||
expect(find.text('item 2'), findsOneWidget);
|
||||
expect(find.text('item 3'), findsOneWidget);
|
||||
|
||||
// Test for removeAllItems.
|
||||
listKey.currentState!.removeAllItems(
|
||||
(BuildContext context, Animation<double> animation) {
|
||||
return const SizedBox(
|
||||
height: 100.0,
|
||||
child: Center(child: Text('removing item')),
|
||||
);
|
||||
},
|
||||
duration: const Duration(milliseconds: 100),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
expect(find.text('removing item'), findsWidgets);
|
||||
expect(find.text('item 0'), findsNothing);
|
||||
expect(find.text('item 1'), findsNothing);
|
||||
expect(find.text('item 2'), findsNothing);
|
||||
expect(find.text('item 3'), findsNothing);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('removing item'), findsNothing);
|
||||
});
|
||||
|
||||
group('SliverAnimatedGrid', () {
|
||||
@@ -224,6 +250,62 @@ void main() {
|
||||
expect(itemRight(2), 300.0);
|
||||
});
|
||||
|
||||
testWidgets('insertAll', (WidgetTester tester) async {
|
||||
final GlobalKey<SliverAnimatedGridState> listKey = GlobalKey<SliverAnimatedGridState>();
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverAnimatedGrid(
|
||||
key: listKey,
|
||||
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
||||
return ScaleTransition(
|
||||
key: ValueKey<int>(index),
|
||||
scale: animation,
|
||||
child: SizedBox(
|
||||
height: 100.0,
|
||||
child: Center(child: Text('item $index')),
|
||||
),
|
||||
);
|
||||
},
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 100.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
double itemScale(int index) =>
|
||||
tester.widget<ScaleTransition>(find.byKey(ValueKey<int>(index), skipOffstage: false)).scale.value;
|
||||
double itemLeft(int index) => tester.getTopLeft(find.byKey(ValueKey<int>(index), skipOffstage: false)).dx;
|
||||
double itemRight(int index) => tester.getTopRight(find.byKey(ValueKey<int>(index), skipOffstage: false)).dx;
|
||||
|
||||
listKey.currentState!.insertAllItems(0, 2, duration: const Duration(milliseconds: 100));
|
||||
await tester.pump();
|
||||
|
||||
// Newly inserted items 0 & 1's scale should animate from 0 to 1
|
||||
expect(itemScale(0), 0.0);
|
||||
expect(itemScale(1), 0.0);
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(itemScale(0), 0.5);
|
||||
expect(itemScale(1), 0.5);
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(itemScale(0), 1.0);
|
||||
expect(itemScale(1), 1.0);
|
||||
|
||||
// The list now contains two fully expanded items at the top:
|
||||
expect(find.text('item 0'), findsOneWidget);
|
||||
expect(find.text('item 1'), findsOneWidget);
|
||||
expect(itemLeft(0), 0.0);
|
||||
expect(itemRight(0), 100.0);
|
||||
expect(itemLeft(1), 100.0);
|
||||
expect(itemRight(1), 200.0);
|
||||
});
|
||||
|
||||
testWidgets('remove', (WidgetTester tester) async {
|
||||
final GlobalKey<SliverAnimatedGridState> listKey = GlobalKey<SliverAnimatedGridState>();
|
||||
final List<int> items = <int>[0, 1, 2];
|
||||
@@ -302,6 +384,58 @@ void main() {
|
||||
expect(itemRight(2), 200.0);
|
||||
});
|
||||
|
||||
testWidgets('removeAll', (WidgetTester tester) async {
|
||||
final GlobalKey<SliverAnimatedGridState> listKey = GlobalKey<SliverAnimatedGridState>();
|
||||
final List<int> items = <int>[0, 1, 2];
|
||||
|
||||
Widget buildItem(BuildContext context, int item, Animation<double> animation) {
|
||||
return ScaleTransition(
|
||||
key: ValueKey<int>(item),
|
||||
scale: animation,
|
||||
child: SizedBox(
|
||||
height: 100.0,
|
||||
child: Center(
|
||||
child: Text('item $item', textDirection: TextDirection.ltr),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverAnimatedGrid(
|
||||
key: listKey,
|
||||
initialItemCount: 3,
|
||||
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
||||
return buildItem(context, items[index], animation);
|
||||
},
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 100.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(find.text('item 0'), findsOneWidget);
|
||||
expect(find.text('item 1'), findsOneWidget);
|
||||
expect(find.text('item 2'), findsOneWidget);
|
||||
|
||||
items.clear();
|
||||
listKey.currentState!.removeAllItems((BuildContext context, Animation<double> animation) => buildItem(context, 0, animation),
|
||||
duration: const Duration(milliseconds: 100),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('item 0'), findsNothing);
|
||||
expect(find.text('item 1'), findsNothing);
|
||||
expect(find.text('item 2'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('works in combination with other slivers', (WidgetTester tester) async {
|
||||
final GlobalKey<SliverAnimatedGridState> listKey = GlobalKey<SliverAnimatedGridState>();
|
||||
|
||||
|
||||
@@ -96,6 +96,33 @@ void main() {
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('removing item'), findsNothing);
|
||||
|
||||
// Test for insertAllItems
|
||||
listKey.currentState!.insertAllItems(0, 2);
|
||||
await tester.pump();
|
||||
expect(find.text('item 2'), findsOneWidget);
|
||||
expect(find.text('item 3'), findsOneWidget);
|
||||
|
||||
// Test for removeAllItems
|
||||
listKey.currentState!.removeAllItems(
|
||||
(BuildContext context, Animation<double> animation) {
|
||||
return const SizedBox(
|
||||
height: 100.0,
|
||||
child: Center(child: Text('removing item')),
|
||||
);
|
||||
},
|
||||
duration: const Duration(milliseconds: 100),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
expect(find.text('removing item'), findsWidgets);
|
||||
expect(find.text('item 0'), findsNothing);
|
||||
expect(find.text('item 1'), findsNothing);
|
||||
expect(find.text('item 2'), findsNothing);
|
||||
expect(find.text('item 3'), findsNothing);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('removing item'), findsNothing);
|
||||
});
|
||||
|
||||
group('SliverAnimatedList', () {
|
||||
@@ -217,6 +244,64 @@ void main() {
|
||||
expect(itemBottom(2), 300.0);
|
||||
});
|
||||
|
||||
// Test for insertAllItems with SliverAnimatedList
|
||||
testWidgets('insertAll', (WidgetTester tester) async {
|
||||
final GlobalKey<SliverAnimatedListState> listKey = GlobalKey<SliverAnimatedListState>();
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverAnimatedList(
|
||||
key: listKey,
|
||||
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
||||
return SizeTransition(
|
||||
key: ValueKey<int>(index),
|
||||
sizeFactor: animation,
|
||||
child: SizedBox(
|
||||
height: 100.0,
|
||||
child: Center(child: Text('item $index')),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
double itemHeight(int index) => tester.getSize(find.byKey(ValueKey<int>(index), skipOffstage: false)).height;
|
||||
double itemTop(int index) => tester.getTopLeft(find.byKey(ValueKey<int>(index), skipOffstage: false)).dy;
|
||||
double itemBottom(int index) => tester.getBottomLeft(find.byKey(ValueKey<int>(index), skipOffstage: false)).dy;
|
||||
|
||||
listKey.currentState!.insertAllItems(
|
||||
0,
|
||||
2,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
// Newly inserted item 0 & 1's height should animate from 0 to 100
|
||||
expect(itemHeight(0), 0.0);
|
||||
expect(itemHeight(1), 0.0);
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(itemHeight(0), 50.0);
|
||||
expect(itemHeight(1), 50.0);
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(itemHeight(0), 100.0);
|
||||
expect(itemHeight(1), 100.0);
|
||||
|
||||
// The list now contains two fully expanded items at the top:
|
||||
expect(find.text('item 0'), findsOneWidget);
|
||||
expect(find.text('item 1'), findsOneWidget);
|
||||
expect(itemTop(0), 0.0);
|
||||
expect(itemBottom(0), 100.0);
|
||||
expect(itemTop(1), 100.0);
|
||||
expect(itemBottom(1), 200.0);
|
||||
});
|
||||
|
||||
// Test for removeAllItems with SliverAnimatedList
|
||||
testWidgets('remove', (WidgetTester tester) async {
|
||||
final GlobalKey<SliverAnimatedListState> listKey = GlobalKey<SliverAnimatedListState>();
|
||||
final List<int> items = <int>[0, 1, 2];
|
||||
@@ -293,6 +378,57 @@ void main() {
|
||||
expect(itemBottom(2), 200.0);
|
||||
});
|
||||
|
||||
// Test for removeAllItems with SliverAnimatedList
|
||||
testWidgets('removeAll', (WidgetTester tester) async {
|
||||
final GlobalKey<SliverAnimatedListState> listKey = GlobalKey<SliverAnimatedListState>();
|
||||
final List<int> items = <int>[0, 1, 2];
|
||||
|
||||
Widget buildItem(BuildContext context, int item, Animation<double> animation) {
|
||||
return SizeTransition(
|
||||
key: ValueKey<int>(item),
|
||||
sizeFactor: animation,
|
||||
child: SizedBox(
|
||||
height: 100.0,
|
||||
child: Center(
|
||||
child: Text('item $item', textDirection: TextDirection.ltr),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverAnimatedList(
|
||||
key: listKey,
|
||||
initialItemCount: 3,
|
||||
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
||||
return buildItem(context, items[index], animation);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('item 0'), findsOneWidget);
|
||||
expect(find.text('item 1'), findsOneWidget);
|
||||
expect(find.text('item 2'), findsOneWidget);
|
||||
|
||||
items.clear();
|
||||
listKey.currentState!.removeAllItems((BuildContext context, Animation<double> animation) => buildItem(context, 0, animation),
|
||||
duration: const Duration(milliseconds: 100),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('item 0'), findsNothing);
|
||||
expect(find.text('item 1'), findsNothing);
|
||||
expect(find.text('item 2'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('works in combination with other slivers', (WidgetTester tester) async {
|
||||
final GlobalKey<SliverAnimatedListState> listKey = GlobalKey<SliverAnimatedListState>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user