Unverified Commit 6707f5ef authored by xubaolin's avatar xubaolin Committed by GitHub

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.
parent bb1271d4
...@@ -18,8 +18,11 @@ import 'viewport_offset.dart'; ...@@ -18,8 +18,11 @@ import 'viewport_offset.dart';
/// Called to get the item extent by the index of item. /// 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]. /// 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. /// Relates the dimensions of the [RenderSliver] during layout.
/// ///
......
...@@ -67,8 +67,17 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -67,8 +67,17 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
return itemExtent * index; return itemExtent * index;
} else { } else {
double offset = 0.0; double offset = 0.0;
double? itemExtent;
for (int i = 0; i < index; i++) { 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; return offset;
} }
...@@ -179,8 +188,13 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -179,8 +188,13 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
return childManager.childCount * itemExtent; return childManager.childCount * itemExtent;
} else { } else {
double offset = 0.0; double offset = 0.0;
double? itemExtent;
for (int i = 0; i < childManager.childCount; i++) { for (int i = 0; i < childManager.childCount; i++) {
offset += itemExtentBuilder!(i, _currentLayoutDimensions); itemExtent = itemExtentBuilder!(i, _currentLayoutDimensions);
if (itemExtent == null) {
break;
}
offset += itemExtent;
} }
return offset; return offset;
} }
...@@ -212,8 +226,17 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -212,8 +226,17 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
} }
double position = 0.0; double position = 0.0;
int index = 0; int index = 0;
double? itemExtent;
while (position < scrollOffset) { 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; ++index;
} }
return index - 1; return index - 1;
...@@ -224,7 +247,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -224,7 +247,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
if (itemExtentBuilder == null) { if (itemExtentBuilder == null) {
extent = itemExtent!; extent = itemExtent!;
} else { } else {
extent = itemExtentBuilder!(index, _currentLayoutDimensions); extent = itemExtentBuilder!(index, _currentLayoutDimensions)!;
} }
return constraints.asBoxConstraints( return constraints.asBoxConstraints(
minExtent: extent, minExtent: extent,
......
...@@ -79,6 +79,17 @@ abstract class RenderSliverBoxChildManager { ...@@ -79,6 +79,17 @@ abstract class RenderSliverBoxChildManager {
/// list). /// list).
int get childCount; 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 /// Called during [RenderSliverMultiBoxAdaptor.adoptChild] or
/// [RenderSliverMultiBoxAdaptor.move]. /// [RenderSliverMultiBoxAdaptor.move].
/// ///
......
...@@ -1506,6 +1506,9 @@ class ListView extends BoxScrollView { ...@@ -1506,6 +1506,9 @@ class ListView extends BoxScrollView {
/// This will be called multiple times during the layout phase of a frame to find /// 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. /// 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 /// Unlike [itemExtent] or [prototypeItem], this allows children to have
/// different extents. /// different extents.
/// ///
......
...@@ -937,15 +937,7 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render ...@@ -937,15 +937,7 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
); );
} }
/// The best available estimate of [childCount], or null if no estimate is available. @override
///
/// 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 => (widget as SliverMultiBoxAdaptorWidget).delegate.estimatedChildCount; int? get estimatedChildCount => (widget as SliverMultiBoxAdaptorWidget).delegate.estimatedChildCount;
@override @override
......
...@@ -98,6 +98,9 @@ class SliverVariedExtentList extends SliverMultiBoxAdaptorWidget { ...@@ -98,6 +98,9 @@ class SliverVariedExtentList extends SliverMultiBoxAdaptorWidget {
)); ));
/// The children extent builder. /// The children extent builder.
///
/// Should return null if asked to build an item extent with a greater index than
/// exists.
final ItemExtentBuilder itemExtentBuilder; final ItemExtentBuilder itemExtentBuilder;
@override @override
......
...@@ -780,6 +780,37 @@ void main() { ...@@ -780,6 +780,37 @@ void main() {
expect(renderObject.clipBehavior, equals(Clip.antiAlias)); 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 // Regression test for https://github.com/flutter/flutter/pull/131393
testWidgets('itemExtentBuilder test', (WidgetTester tester) async { testWidgets('itemExtentBuilder test', (WidgetTester tester) async {
final ScrollController controller = ScrollController(); final ScrollController controller = ScrollController();
......
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