diff --git a/packages/flutter/lib/src/widgets/animated_list.dart b/packages/flutter/lib/src/widgets/animated_list.dart index 9b8bc11333..4d0195980a 100644 --- a/packages/flutter/lib/src/widgets/animated_list.dart +++ b/packages/flutter/lib/src/widgets/animated_list.dart @@ -514,7 +514,12 @@ class SliverAnimatedListState extends State with TickerProvi return SliverChildBuilderDelegate( _itemBuilder, childCount: _itemsCount, - findChildIndexCallback: widget.findChildIndexCallback, + findChildIndexCallback: widget.findChildIndexCallback == null + ? null + : (Key key) { + final int? index = widget.findChildIndexCallback!(key); + return index != null ? _indexToItemIndex(index) : null; + }, ); } diff --git a/packages/flutter/test/widgets/animated_list_test.dart b/packages/flutter/test/widgets/animated_list_test.dart index c5dd7519d0..6bc5042d54 100644 --- a/packages/flutter/test/widgets/animated_list_test.dart +++ b/packages/flutter/test/widgets/animated_list_test.dart @@ -357,6 +357,67 @@ void main() { expect(find.text('removing'), findsNothing); expect(tester.getTopLeft(find.text('item 0')).dy, 200); }); + + testWidgets('passes correctly derived index of findChildIndexCallback to the inner SliverChildBuilderDelegate', (WidgetTester tester) async { + final List items = [0, 1, 2, 3]; + final GlobalKey listKey = GlobalKey(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: CustomScrollView( + slivers: [ + SliverAnimatedList( + key: listKey, + initialItemCount: items.length, + itemBuilder: (BuildContext context, int index, Animation animation) { + return _StatefulListItem( + key: ValueKey(items[index]), + index: index, + ); + }, + findChildIndexCallback: (Key key) { + final int index = items.indexOf((key as ValueKey).value); + return index == -1 ? null : index; + }, + ), + ], + ), + ), + ); + + // get all list entries in order + final List listEntries = find.byType(Text).evaluate().map((Element e) => e.widget as Text).toList(); + + // check that the list is rendered in the correct order + expect(listEntries[0].data, equals('item 0')); + expect(listEntries[1].data, equals('item 1')); + expect(listEntries[2].data, equals('item 2')); + expect(listEntries[3].data, equals('item 3')); + + + // delete one item + listKey.currentState?.removeItem(0, (BuildContext context, Animation animation) { + return Container(); + }); + + // delete from list + items.removeAt(0); + + // reorder list + items.insert(0, items.removeLast()); + + // render with new list order + await tester.pumpAndSettle(); + + // get all list entries in order + final List reorderedListEntries = find.byType(Text).evaluate().map((Element e) => e.widget as Text).toList(); + + // check that the stateful items of the list are rendered in the order provided by findChildIndexCallback + expect(reorderedListEntries[0].data, equals('item 3')); + expect(reorderedListEntries[1].data, equals('item 1')); + expect(reorderedListEntries[2].data, equals('item 2')); + }); }); testWidgets( @@ -428,3 +489,25 @@ void main() { expect(tester.widget(find.byType(CustomScrollView)).clipBehavior, clipBehavior); }); } + + +class _StatefulListItem extends StatefulWidget { + const _StatefulListItem({ + super.key, + required this.index, + }); + + final int index; + + @override + _StatefulListItemState createState() => _StatefulListItemState(); +} + +class _StatefulListItemState extends State<_StatefulListItem> { + late final int number = widget.index; + + @override + Widget build(BuildContext context) { + return Text('item $number'); + } +}