Unverified Commit ea03ac2b authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Revert "Sliver animated list (#28834)" (#32135)

This reverts commit d2de911d.
parent d2de911d
...@@ -10,7 +10,6 @@ import 'framework.dart'; ...@@ -10,7 +10,6 @@ import 'framework.dart';
import 'scroll_controller.dart'; import 'scroll_controller.dart';
import 'scroll_physics.dart'; import 'scroll_physics.dart';
import 'scroll_view.dart'; import 'scroll_view.dart';
import 'sliver.dart';
import 'ticker_provider.dart'; import 'ticker_provider.dart';
/// Signature for the builder callback used by [AnimatedList]. /// Signature for the builder callback used by [AnimatedList].
...@@ -76,13 +75,11 @@ class AnimatedList extends StatefulWidget { ...@@ -76,13 +75,11 @@ class AnimatedList extends StatefulWidget {
/// [AnimatedListState.removeItem] removes an item immediately. /// [AnimatedListState.removeItem] removes an item immediately.
final AnimatedListItemBuilder itemBuilder; final AnimatedListItemBuilder itemBuilder;
/// {@template flutter.widgets.animatedList.initialItemCount}
/// The number of items the list will start with. /// The number of items the list will start with.
/// ///
/// The appearance of the initial items is not animated. They /// The appearance of the initial items is not animated. They
/// are created, as needed, by [itemBuilder] with an animation parameter /// are created, as needed, by [itemBuilder] with an animation parameter
/// of [kAlwaysCompleteAnimation]. /// of [kAlwaysCompleteAnimation].
/// {@endtemplate}
final int initialItemCount; final int initialItemCount;
/// The axis along which the scroll view scrolls. /// The axis along which the scroll view scrolls.
...@@ -210,150 +207,6 @@ class AnimatedList extends StatefulWidget { ...@@ -210,150 +207,6 @@ class AnimatedList extends StatefulWidget {
/// [AnimatedList] item input handlers can also refer to their [AnimatedListState] /// [AnimatedList] item input handlers can also refer to their [AnimatedListState]
/// with the static [AnimatedList.of] method. /// with the static [AnimatedList.of] method.
class AnimatedListState extends State<AnimatedList> with TickerProviderStateMixin<AnimatedList> { class AnimatedListState extends State<AnimatedList> with TickerProviderStateMixin<AnimatedList> {
final GlobalKey<SliverAnimatedListState> _sliverAnimatedListKey = GlobalKey();
/// Insert an item at [index] and start an animation that will be passed
/// to [AnimatedList.itemBuilder] when the item is visible.
///
/// This method's semantics are the same as Dart's [List.insert] method:
/// it increases the length of the list by one and shifts all items at or
/// after [index] towards the end of the list.
void insertItem(int index, { Duration duration = _kDuration }) {
_sliverAnimatedListKey.currentState.insertItem(index, duration: duration);
}
/// Remove the item at [index] and start an animation that will be passed
/// to [builder] when the item is visible.
///
/// Items are removed immediately. After an item has been removed, its index
/// will no longer be passed to the [AnimatedList.itemBuilder]. However the
/// item will still appear in the list for [duration] and during that time
/// [builder] must construct its widget as needed.
///
/// This method's semantics are the same as Dart's [List.remove] method:
/// it decreases the length of the list by one and shifts all items at or
/// before [index] towards the beginning of the list.
void removeItem(int index, AnimatedListRemovedItemBuilder builder, { Duration duration = _kDuration }) {
_sliverAnimatedListKey.currentState.removeItem(index, builder, duration: duration);
}
@override
Widget build(BuildContext context) {
return CustomScrollView(
scrollDirection: widget.scrollDirection,
reverse: widget.reverse,
controller: widget.controller,
primary: widget.primary,
physics: widget.physics,
shrinkWrap: widget.shrinkWrap,
slivers: <Widget>[
SliverPadding(
padding: widget.padding ?? const EdgeInsets.all(0),
sliver: SliverAnimatedList(
key: _sliverAnimatedListKey,
itemBuilder: widget.itemBuilder,
initialItemCount: widget.initialItemCount,
),
),
],
);
}
}
/// A sliver that animates items when they are inserted or removed.
///
/// This widget's [SliverAnimatedListState] can be used to dynamically insert or
/// remove items. To refer to the [SliverAnimatedListState] either provide a
/// [GlobalKey] or use the static [SliverAnimatedList.of] method from an item's
/// input callback.
///
/// See also:
///
/// * [SliverList], which does not animate items when they are inserted or removed.
class SliverAnimatedList extends StatefulWidget {
/// Creates a sliver that animates items when they are inserted or removed.
const SliverAnimatedList({
Key key,
@required this.itemBuilder,
this.initialItemCount = 0,
}) : assert(itemBuilder != null),
assert(initialItemCount != null && initialItemCount >= 0),
super(key: key);
/// Called, as needed, to build list item widgets.
///
/// List items are only built when they're scrolled into view.
///
/// The [AnimatedListItemBuilder] index parameter indicates the item's
/// position in the list. The value of the index parameter will be between 0
/// and [initialItemCount] plus the total number of items that have been
/// inserted with [SliverAnimatedListState.insertItem] and less the total
/// number of items that have been removed with
/// [SliverAnimatedListState.removeItem].
///
/// Implementations of this callback should assume that
/// [SliverAnimatedListState.removeItem] removes an item immediately.
final AnimatedListItemBuilder itemBuilder;
/// {@macro flutter.widgets.animatedList.initialItemCount}
final int initialItemCount;
@override
SliverAnimatedListState createState() => SliverAnimatedListState();
/// The state from the closest instance of this class that encloses the given context.
///
/// This method is typically used by [SliverAnimatedList] item widgets that
/// insert or remove items in response to user input.
///
/// ```dart
/// SliverAnimatedListState animatedList = SliverAnimatedList.of(context);
/// ```
static SliverAnimatedListState of(BuildContext context, {bool nullOk = false}) {
assert(context != null);
assert(nullOk != null);
final SliverAnimatedListState result = context.ancestorStateOfType(const TypeMatcher<SliverAnimatedListState>());
if (nullOk || result != null)
return result;
throw FlutterError(
'SliverAnimatedList.of() called with a context that does not contain a SliverAnimatedList.\n'
'No SliverAnimatedListState ancestor could be found starting from the '
'context that was passed to SliverAnimatedListState.of(). '
'This can happen when the context provided is from the same StatefulWidget that '
'built the AnimatedList. Please see the SliverAnimatedList documentation '
'for examples of how to refer to an AnimatedListState object: '
' https://docs.flutter.io/flutter/widgets/SliverAnimatedListState-class.html \n'
'The context used was:\n'
' $context');
}
}
/// The state for a sliver that animates items when they are
/// inserted or removed.
///
/// When an item is inserted with [insertItem] an animation begins running. The
/// animation is passed to [SliverAnimatedList.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.
///
/// An app that needs to insert or remove items in response to an event
/// can refer to the [SliverAnimatedList]'s state with a global key:
///
/// ```dart
/// GlobalKey<SliverAnimatedListState> listKey = GlobalKey<SliverAnimatedListState>();
/// ...
/// SliverAnimatedList(key: listKey, ...);
/// ...
/// listKey.currentState.insert(123);
/// ```
///
/// [SliverAnimatedList] item input handlers can also refer to their
/// [SliverAnimatedListState] with the static [SliverAnimatedList.of] method.
class SliverAnimatedListState extends State<SliverAnimatedList> with TickerProviderStateMixin {
final List<_ActiveItem> _incomingItems = <_ActiveItem>[]; final List<_ActiveItem> _incomingItems = <_ActiveItem>[];
final List<_ActiveItem> _outgoingItems = <_ActiveItem>[]; final List<_ActiveItem> _outgoingItems = <_ActiveItem>[];
int _itemsCount = 0; int _itemsCount = 0;
...@@ -366,9 +219,10 @@ class SliverAnimatedListState extends State<SliverAnimatedList> with TickerProvi ...@@ -366,9 +219,10 @@ class SliverAnimatedListState extends State<SliverAnimatedList> with TickerProvi
@override @override
void dispose() { void dispose() {
for (_ActiveItem item in _incomingItems.followedBy(_outgoingItems)) { for (_ActiveItem item in _incomingItems)
item.controller.dispose();
for (_ActiveItem item in _outgoingItems)
item.controller.dispose(); item.controller.dispose();
}
super.dispose(); super.dispose();
} }
...@@ -411,12 +265,8 @@ class SliverAnimatedListState extends State<SliverAnimatedList> with TickerProvi ...@@ -411,12 +265,8 @@ class SliverAnimatedListState extends State<SliverAnimatedList> with TickerProvi
return index; return index;
} }
SliverChildDelegate _createDelegate() { /// Insert an item at [index] and start an animation that will be passed
return SliverChildBuilderDelegate(_itemBuilder, childCount: _itemsCount); /// to [AnimatedList.itemBuilder] when the item is visible.
}
/// Insert an item at [index] and start an animation that will be passed to
/// [SliverAnimatedList.itemBuilder] when the item is visible.
/// ///
/// This method's semantics are the same as Dart's [List.insert] method: /// This method's semantics are the same as Dart's [List.insert] method:
/// it increases the length of the list by one and shifts all items at or /// it increases the length of the list by one and shifts all items at or
...@@ -457,8 +307,8 @@ class SliverAnimatedListState extends State<SliverAnimatedList> with TickerProvi ...@@ -457,8 +307,8 @@ class SliverAnimatedListState extends State<SliverAnimatedList> with TickerProvi
/// to [builder] when the item is visible. /// to [builder] when the item is visible.
/// ///
/// Items are removed immediately. After an item has been removed, its index /// Items are removed immediately. After an item has been removed, its index
/// will no longer be passed to the [SliverAnimatedList.itemBuilder]. However /// will no longer be passed to the [AnimatedList.itemBuilder]. However the
/// the item will still appear in the list for [duration] and during that time /// item will still appear in the list for [duration] and during that time
/// [builder] must construct its widget as needed. /// [builder] must construct its widget as needed.
/// ///
/// This method's semantics are the same as Dart's [List.remove] method: /// This method's semantics are the same as Dart's [List.remove] method:
...@@ -515,8 +365,16 @@ class SliverAnimatedListState extends State<SliverAnimatedList> with TickerProvi ...@@ -515,8 +365,16 @@ class SliverAnimatedListState extends State<SliverAnimatedList> with TickerProvi
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliverList( return ListView.builder(
delegate: _createDelegate(), itemBuilder: _itemBuilder,
itemCount: _itemsCount,
scrollDirection: widget.scrollDirection,
reverse: widget.reverse,
controller: widget.controller,
primary: widget.primary,
physics: widget.physics,
shrinkWrap: widget.shrinkWrap,
padding: widget.padding,
); );
} }
} }
...@@ -6,64 +6,13 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -6,64 +6,13 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
void main() { void main() {
testWidgets('AnimatedList', (WidgetTester tester) async { testWidgets('AnimatedList initialItemCount', (WidgetTester tester) async {
final AnimatedListItemBuilder builder = (BuildContext context, int index, Animation<double> animation) {
return SizedBox(
height: 100.0,
child: Center(
child: Text('item $index'),
),
);
};
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: AnimatedList(
key: listKey,
initialItemCount: 2,
itemBuilder: builder,
),
),
);
expect(find.byWidgetPredicate((Widget widget) {
return widget is SliverAnimatedList
&& widget.initialItemCount == 2
&& widget.itemBuilder == builder;
}), findsOneWidget);
listKey.currentState.insertItem(0);
await tester.pump();
expect(find.text('item 2'), findsOneWidget);
listKey.currentState.removeItem(2, (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'), findsOneWidget);
expect(find.text('item 2'), findsNothing);
await tester.pumpAndSettle(const Duration(milliseconds: 100));
expect(find.text('removing item'), findsNothing);
});
group('SliverAnimatedList', () {
testWidgets('initialItemCount', (WidgetTester tester) async {
final Map<int, Animation<double>> animations = <int, Animation<double>>{}; final Map<int, Animation<double>> animations = <int, Animation<double>>{};
await tester.pumpWidget( await tester.pumpWidget(
Directionality( Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: CustomScrollView( child: AnimatedList(
slivers: <Widget>[
SliverAnimatedList(
initialItemCount: 2, initialItemCount: 2,
itemBuilder: (BuildContext context, int index, Animation<double> animation) { itemBuilder: (BuildContext context, int index, Animation<double> animation) {
animations[index] = animation; animations[index] = animation;
...@@ -74,8 +23,6 @@ void main() { ...@@ -74,8 +23,6 @@ void main() {
), ),
); );
}, },
)
],
), ),
), ),
); );
...@@ -88,15 +35,13 @@ void main() { ...@@ -88,15 +35,13 @@ void main() {
expect(animations[1].value, 1.0); expect(animations[1].value, 1.0);
}); });
testWidgets('insert', (WidgetTester tester) async { testWidgets('AnimatedList insert', (WidgetTester tester) async {
final GlobalKey<SliverAnimatedListState> listKey = GlobalKey<SliverAnimatedListState>(); final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
await tester.pumpWidget( await tester.pumpWidget(
Directionality( Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: CustomScrollView( child: AnimatedList(
slivers: <Widget>[
SliverAnimatedList(
key: listKey, key: listKey,
itemBuilder: (BuildContext context, int index, Animation<double> animation) { itemBuilder: (BuildContext context, int index, Animation<double> animation) {
return SizeTransition( return SizeTransition(
...@@ -111,8 +56,6 @@ void main() { ...@@ -111,8 +56,6 @@ void main() {
), ),
); );
}, },
)
],
), ),
), ),
); );
...@@ -166,8 +109,8 @@ void main() { ...@@ -166,8 +109,8 @@ void main() {
expect(itemBottom(2), 300.0); expect(itemBottom(2), 300.0);
}); });
testWidgets('remove', (WidgetTester tester) async { testWidgets('AnimatedList remove', (WidgetTester tester) async {
final GlobalKey<SliverAnimatedListState> listKey = GlobalKey<SliverAnimatedListState>(); final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
final List<int> items = <int>[0, 1, 2]; final List<int> items = <int>[0, 1, 2];
Widget buildItem(BuildContext context, int item, Animation<double> animation) { Widget buildItem(BuildContext context, int item, Animation<double> animation) {
...@@ -187,16 +130,12 @@ void main() { ...@@ -187,16 +130,12 @@ void main() {
await tester.pumpWidget( await tester.pumpWidget(
Directionality( Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: CustomScrollView( child: AnimatedList(
slivers: <Widget>[
SliverAnimatedList(
key: listKey, key: listKey,
initialItemCount: 3, initialItemCount: 3,
itemBuilder: (BuildContext context, int index, Animation<double> animation) { itemBuilder: (BuildContext context, int index, Animation<double> animation) {
return buildItem(context, items[index], animation); return buildItem(context, items[index], animation);
}, },
)
],
), ),
), ),
); );
...@@ -241,67 +180,4 @@ void main() { ...@@ -241,67 +180,4 @@ void main() {
expect(itemTop(2), 100.0); expect(itemTop(2), 100.0);
expect(itemBottom(2), 200.0); expect(itemBottom(2), 200.0);
}); });
testWidgets('works in combination with other slivers', (WidgetTester tester) async {
final GlobalKey<SliverAnimatedListState> listKey = GlobalKey<SliverAnimatedListState>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
slivers: <Widget>[
const SliverList(
delegate: SliverChildListDelegate(<Widget>[
SizedBox(height: 100),
SizedBox(height: 100),
]),
),
SliverAnimatedList(
key: listKey,
initialItemCount: 3,
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
return SizedBox(
height: 100,
child: Text('item $index'),
);
},
),
],
),
),
);
expect(tester.getTopLeft(find.text('item 0')).dy, 200);
expect(tester.getTopLeft(find.text('item 1')).dy, 300);
listKey.currentState.insertItem(3);
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.text('item 3')).dy, 500);
listKey.currentState.removeItem(0,
(BuildContext context, Animation<double> animation) {
return SizeTransition(
sizeFactor: animation,
key: const ObjectKey('removing'),
child: const SizedBox(
height: 100,
child: Text('removing'),
),
);
},
duration: const Duration(seconds: 1),
);
await tester.pump();
expect(find.text('item 3'), findsNothing);
await tester.pump(const Duration(milliseconds: 500));
expect(tester.getSize(find.byKey(const ObjectKey('removing'))).height, 50);
expect(tester.getTopLeft(find.text('item 0')).dy, 250);
await tester.pumpAndSettle();
expect(find.text('removing'), findsNothing);
expect(tester.getTopLeft(find.text('item 0')).dy, 200);
});
});
} }
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