[RenderListWheelViewport] Update content dimensions to prevent scroll offset changes (#96102)
diff --git a/packages/flutter/lib/src/rendering/list_wheel_viewport.dart b/packages/flutter/lib/src/rendering/list_wheel_viewport.dart
index 08f5240..b9a8189 100644
--- a/packages/flutter/lib/src/rendering/list_wheel_viewport.dart
+++ b/packages/flutter/lib/src/rendering/list_wheel_viewport.dart
@@ -663,12 +663,10 @@
/// by [childManager].
@override
void performLayout() {
- final BoxConstraints childConstraints =
- constraints.copyWith(
- minHeight: _itemExtent,
- maxHeight: _itemExtent,
- minWidth: 0.0,
- );
+ // Apply the dimensions first in case it changes the scroll offset which
+ // determines what should be shown.
+ offset.applyViewportDimension(_viewportExtent);
+ offset.applyContentDimensions(_minEstimatedScrollExtent, _maxEstimatedScrollExtent);
// The height, in pixel, that children will be visible and might be laid out
// and painted.
@@ -680,7 +678,7 @@
visibleHeight *= 2;
final double firstVisibleOffset =
- offset.pixels + _itemExtent / 2 - visibleHeight / 2;
+ offset.pixels + _itemExtent / 2 - visibleHeight / 2;
final double lastVisibleOffset = firstVisibleOffset + visibleHeight;
// The index range that we want to spawn children. We find indexes that
@@ -721,6 +719,11 @@
_destroyChild(firstChild!);
}
+ final BoxConstraints childConstraints = constraints.copyWith(
+ minHeight: _itemExtent,
+ maxHeight: _itemExtent,
+ minWidth: 0.0,
+ );
// If there is no child at this stage, we add the first one that is in
// target range.
if (childCount == 0) {
@@ -759,8 +762,6 @@
_layoutChild(lastChild!, childConstraints, ++currentLastIndex);
}
- offset.applyViewportDimension(_viewportExtent);
-
// Applying content dimensions bases on how the childManager builds widgets:
// if it is available to provide a child just out of target range, then
// we don't know whether there's a limit yet, and set the dimension to the
@@ -770,16 +771,16 @@
? _minEstimatedScrollExtent
: indexToScrollOffset(targetFirstIndex);
final double maxScrollExtent = childManager.childExistsAt(targetLastIndex + 1)
- ? _maxEstimatedScrollExtent
- : indexToScrollOffset(targetLastIndex);
+ ? _maxEstimatedScrollExtent
+ : indexToScrollOffset(targetLastIndex);
offset.applyContentDimensions(minScrollExtent, maxScrollExtent);
}
bool _shouldClipAtCurrentOffset() {
final double highestUntransformedPaintY =
- _getUntransformedPaintingCoordinateY(0.0);
+ _getUntransformedPaintingCoordinateY(0.0);
return highestUntransformedPaintY < 0.0
- || size.height < highestUntransformedPaintY + _maxEstimatedScrollExtent + _itemExtent;
+ || size.height < highestUntransformedPaintY + _maxEstimatedScrollExtent + _itemExtent;
}
@override
diff --git a/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart b/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart
index 31eead2..a7740e9 100644
--- a/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart
@@ -766,24 +766,26 @@
}
}
+ bool _handleScrollNotification(ScrollNotification notification) {
+ if (notification.depth == 0
+ && widget.onSelectedItemChanged != null
+ && notification is ScrollUpdateNotification
+ && notification.metrics is FixedExtentMetrics) {
+ final FixedExtentMetrics metrics = notification.metrics as FixedExtentMetrics;
+ final int currentItemIndex = metrics.itemIndex;
+ if (currentItemIndex != _lastReportedItemIndex) {
+ _lastReportedItemIndex = currentItemIndex;
+ final int trueIndex = widget.childDelegate.trueIndexOf(currentItemIndex);
+ widget.onSelectedItemChanged!(trueIndex);
+ }
+ }
+ return false;
+ }
+
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
- onNotification: (ScrollNotification notification) {
- if (notification.depth == 0
- && widget.onSelectedItemChanged != null
- && notification is ScrollUpdateNotification
- && notification.metrics is FixedExtentMetrics) {
- final FixedExtentMetrics metrics = notification.metrics as FixedExtentMetrics;
- final int currentItemIndex = metrics.itemIndex;
- if (currentItemIndex != _lastReportedItemIndex) {
- _lastReportedItemIndex = currentItemIndex;
- final int trueIndex = widget.childDelegate.trueIndexOf(currentItemIndex);
- widget.onSelectedItemChanged!(trueIndex);
- }
- }
- return false;
- },
+ onNotification: _handleScrollNotification,
child: _FixedExtentScrollable(
controller: scrollController,
physics: widget.physics,
diff --git a/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart b/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart
index 395b56a..ba9b6fe 100644
--- a/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart
+++ b/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart
@@ -230,6 +230,7 @@
});
testWidgets('child builder with lower and upper limits', (WidgetTester tester) async {
+ // Adjust the content dimensions at the end of `RenderListWheelViewport.performLayout()`
final List<int> paintedChildren = <int>[];
final FixedExtentScrollController controller =
@@ -285,6 +286,69 @@
});
group('layout', () {
+ // Regression test for https://github.com/flutter/flutter/issues/90953
+ testWidgets('ListWheelScrollView childDelegate update test 2', (WidgetTester tester) async {
+ final FixedExtentScrollController controller = FixedExtentScrollController( initialItem: 2 );
+ Widget buildFrame(int childCount) {
+ return Directionality(
+ textDirection: TextDirection.ltr,
+ child: ListWheelScrollView.useDelegate(
+ controller: controller,
+ itemExtent: 400.0,
+ onSelectedItemChanged: (_) { },
+ childDelegate: ListWheelChildBuilderDelegate(
+ childCount: childCount,
+ builder: (BuildContext context, int index) {
+ return SizedBox(
+ width: 400.0,
+ height: 400.0,
+ child: Text(index.toString()),
+ );
+ },
+ ),
+ ),
+ );
+ }
+
+ await tester.pumpWidget(buildFrame(5));
+ expect(find.text('0'), findsNothing);
+ expect(tester.renderObject(find.text('1')).attached, true);
+ expect(tester.renderObject(find.text('2')).attached, true);
+ expect(tester.renderObject(find.text('3')).attached, true);
+ expect(find.text('4'), findsNothing);
+
+ // Remove the last 3 items.
+ await tester.pumpWidget(buildFrame(2));
+ expect(tester.renderObject(find.text('0')).attached, true);
+ expect(tester.renderObject(find.text('1')).attached, true);
+ expect(find.text('3'), findsNothing);
+
+ // Add 3 items at the end.
+ await tester.pumpWidget(buildFrame(5));
+ expect(tester.renderObject(find.text('0')).attached, true);
+ expect(tester.renderObject(find.text('1')).attached, true);
+ expect(tester.renderObject(find.text('2')).attached, true);
+ expect(find.text('3'), findsNothing);
+ expect(find.text('4'), findsNothing);
+
+
+ // Scroll to the last item.
+ final TestGesture scrollGesture = await tester.startGesture(const Offset(10.0, 10.0));
+ await scrollGesture.moveBy(const Offset(0.0, -1200.0));
+ await tester.pump();
+ expect(find.text('0'), findsNothing);
+ expect(find.text('1'), findsNothing);
+ expect(find.text('2'), findsNothing);
+ expect(tester.renderObject(find.text('3')).attached, true);
+ expect(tester.renderObject(find.text('4')).attached, true);
+
+ // Remove the last 3 items.
+ await tester.pumpWidget(buildFrame(2));
+ expect(tester.renderObject(find.text('0')).attached, true);
+ expect(tester.renderObject(find.text('1')).attached, true);
+ expect(find.text('3'), findsNothing);
+ });
+
// Regression test for https://github.com/flutter/flutter/issues/58144
testWidgets('ListWheelScrollView childDelegate update test', (WidgetTester tester) async {
final FixedExtentScrollController controller = FixedExtentScrollController();