Change ItemExtentBuilder's return value nullable (#142428)

Fixes https://github.com/flutter/flutter/issues/138912

Change `ItemExtentBuilder`'s return value nullable, it should return null if asked to build an item extent with a greater index than exists.
This commit is contained in:
xubaolin 2024-02-21 06:14:00 +08:00 committed by GitHub
parent bb1271d483
commit 6707f5efbe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 80 additions and 14 deletions

View file

@ -18,8 +18,11 @@ import 'viewport_offset.dart';
/// Called to get the item extent by the index of item.
///
/// Should return null if asked to build an item extent with a greater index than
/// exists.
///
/// Used by [ListView.itemExtentBuilder] and [SliverVariedExtentList.itemExtentBuilder].
typedef ItemExtentBuilder = double Function(int index, SliverLayoutDimensions dimensions);
typedef ItemExtentBuilder = double? Function(int index, SliverLayoutDimensions dimensions);
/// Relates the dimensions of the [RenderSliver] during layout.
///

View file

@ -67,8 +67,17 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
return itemExtent * index;
} else {
double offset = 0.0;
double? itemExtent;
for (int i = 0; i < index; i++) {
offset += itemExtentBuilder!(i, _currentLayoutDimensions);
final int? childCount = childManager.estimatedChildCount;
if (childCount != null && i > childCount - 1) {
break;
}
itemExtent = itemExtentBuilder!(i, _currentLayoutDimensions);
if (itemExtent == null) {
break;
}
offset += itemExtent;
}
return offset;
}
@ -179,8 +188,13 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
return childManager.childCount * itemExtent;
} else {
double offset = 0.0;
double? itemExtent;
for (int i = 0; i < childManager.childCount; i++) {
offset += itemExtentBuilder!(i, _currentLayoutDimensions);
itemExtent = itemExtentBuilder!(i, _currentLayoutDimensions);
if (itemExtent == null) {
break;
}
offset += itemExtent;
}
return offset;
}
@ -212,8 +226,17 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
}
double position = 0.0;
int index = 0;
double? itemExtent;
while (position < scrollOffset) {
position += callback(index, _currentLayoutDimensions);
final int? childCount = childManager.estimatedChildCount;
if (childCount != null && index > childCount - 1) {
break;
}
itemExtent = callback(index, _currentLayoutDimensions);
if (itemExtent == null) {
break;
}
position += itemExtent;
++index;
}
return index - 1;
@ -224,7 +247,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
if (itemExtentBuilder == null) {
extent = itemExtent!;
} else {
extent = itemExtentBuilder!(index, _currentLayoutDimensions);
extent = itemExtentBuilder!(index, _currentLayoutDimensions)!;
}
return constraints.asBoxConstraints(
minExtent: extent,

View file

@ -79,6 +79,17 @@ abstract class RenderSliverBoxChildManager {
/// list).
int get childCount;
/// The best available estimate of [childCount], or null if no estimate is available.
///
/// This differs from [childCount] in that [childCount] never returns null (and must
/// not be accessed if the child count is not yet available, meaning the [createChild]
/// method has not been provided an index that does not create a child).
///
/// See also:
///
/// * [SliverChildDelegate.estimatedChildCount], to which this getter defers.
int? get estimatedChildCount => null;
/// Called during [RenderSliverMultiBoxAdaptor.adoptChild] or
/// [RenderSliverMultiBoxAdaptor.move].
///

View file

@ -1506,6 +1506,9 @@ class ListView extends BoxScrollView {
/// This will be called multiple times during the layout phase of a frame to find
/// the items that should be loaded by the lazy loading process.
///
/// Should return null if asked to build an item extent with a greater index than
/// exists.
///
/// Unlike [itemExtent] or [prototypeItem], this allows children to have
/// different extents.
///

View file

@ -937,15 +937,7 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
);
}
/// The best available estimate of [childCount], or null if no estimate is available.
///
/// This differs from [childCount] in that [childCount] never returns null (and must
/// not be accessed if the child count is not yet available, meaning the [createChild]
/// method has not been provided an index that does not create a child).
///
/// See also:
///
/// * [SliverChildDelegate.estimatedChildCount], to which this getter defers.
@override
int? get estimatedChildCount => (widget as SliverMultiBoxAdaptorWidget).delegate.estimatedChildCount;
@override

View file

@ -98,6 +98,9 @@ class SliverVariedExtentList extends SliverMultiBoxAdaptorWidget {
));
/// The children extent builder.
///
/// Should return null if asked to build an item extent with a greater index than
/// exists.
final ItemExtentBuilder itemExtentBuilder;
@override

View file

@ -780,6 +780,37 @@ void main() {
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
});
// Regression test for https://github.com/flutter/flutter/pull/138912
testWidgets('itemExtentBuilder should respect item count', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
addTearDown(controller.dispose);
final List<double> numbers = <double>[
10, 20, 30, 40, 50,
];
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: ListView.builder(
controller: controller,
itemCount: numbers.length,
itemExtentBuilder: (int index, SliverLayoutDimensions dimensions) {
return numbers[index];
},
itemBuilder: (BuildContext context, int index) {
return SizedBox(
height: numbers[index],
child: Text('Item $index'),
);
},
),
),
);
expect(find.text('Item 0'), findsOneWidget);
expect(find.text('Item 4'), findsOneWidget);
expect(find.text('Item 5'), findsNothing);
});
// Regression test for https://github.com/flutter/flutter/pull/131393
testWidgets('itemExtentBuilder test', (WidgetTester tester) async {
final ScrollController controller = ScrollController();