This reverts commit d2de911d50ea792d6743b2cdc74e8daf26112b91.
This commit is contained in:
parent
d2de911d50
commit
ea03ac2b84
@ -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 {
|
|||||||
/// [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 {
|
|||||||
/// [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
|
|||||||
|
|
||||||
@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
|
|||||||
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
|
|||||||
/// 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
|
|||||||
|
|
||||||
@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';
|
|||||||
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() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -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() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -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() {
|
|||||||
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() {
|
|||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user