From 3ed1ca72e15d0ad300067aefd28f3cc23ac8c428 Mon Sep 17 00:00:00 2001 From: yim Date: Thu, 10 Jul 2025 08:55:05 +0800 Subject: [PATCH] Fix the issue with `SliverMainAxisGroups` growing in the reverse direction during layout. (#171005) Fixes: #170991 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../lib/src/rendering/sliver_group.dart | 20 ++++++++--- .../widgets/sliver_main_axis_group_test.dart | 35 +++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/rendering/sliver_group.dart b/packages/flutter/lib/src/rendering/sliver_group.dart index cc181494632..f10415f1b60 100644 --- a/packages/flutter/lib/src/rendering/sliver_group.dart +++ b/packages/flutter/lib/src/rendering/sliver_group.dart @@ -263,7 +263,14 @@ class RenderSliverMainAxisGroup extends RenderSliver double maxPaintExtent = 0; double paintOffset = constraints.overlap; - RenderSliver? child = firstChild; + final ( + RenderSliver? leadingChild, + RenderSliver? Function(RenderSliver child) advance, + ) = switch (constraints.growthDirection) { + GrowthDirection.forward => (firstChild, childAfter), + GrowthDirection.reverse => (lastChild, childBefore), + }; + RenderSliver? child = leadingChild; while (child != null) { final double beforeOffsetPaintExtent = calculatePaintOffset( constraints, @@ -298,7 +305,7 @@ class RenderSliverMainAxisGroup extends RenderSliver layoutOffset += childLayoutGeometry.layoutExtent; maxPaintExtent += childLayoutGeometry.maxPaintExtent; paintOffset = math.max(childPaintOffset + childLayoutGeometry.paintExtent, paintOffset); - child = childAfter(child); + child = advance(child); assert(() { if (child != null && maxPaintExtent.isInfinite) { throw FlutterError( @@ -349,11 +356,14 @@ class RenderSliverMainAxisGroup extends RenderSliver // Update the children's paintOffset based on the direction again, which // must be done after obtaining the `paintExtent`. - child = firstChild; + child = leadingChild; while (child != null) { final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; - childParentData.paintOffset = switch (constraints.axisDirection) { + childParentData.paintOffset = switch (applyGrowthDirectionToAxisDirection( + constraints.axisDirection, + constraints.growthDirection, + )) { AxisDirection.up => Offset( 0.0, paintExtent - childParentData.paintOffset.dy - child.geometry!.paintExtent, @@ -364,7 +374,7 @@ class RenderSliverMainAxisGroup extends RenderSliver ), AxisDirection.right || AxisDirection.down => childParentData.paintOffset, }; - child = childAfter(child); + child = advance(child); } } diff --git a/packages/flutter/test/widgets/sliver_main_axis_group_test.dart b/packages/flutter/test/widgets/sliver_main_axis_group_test.dart index e475e47a2a2..2910c130c10 100644 --- a/packages/flutter/test/widgets/sliver_main_axis_group_test.dart +++ b/packages/flutter/test/widgets/sliver_main_axis_group_test.dart @@ -1062,6 +1062,41 @@ void main() { await tester.pumpAndSettle(); expect(tester.takeException(), isNull); }); + + testWidgets('SliverMainAxisGroup with center', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + addTearDown(controller.dispose); + const Key centerKey = Key('center'); + await tester.pumpWidget( + MaterialApp( + home: CustomScrollView( + center: centerKey, + controller: controller, + slivers: const [ + SliverMainAxisGroup( + slivers: [ + SliverToBoxAdapter(child: SizedBox(height: 50, child: Text('-2'))), + SliverToBoxAdapter(child: SizedBox(height: 50, child: Text('-1'))), + ], + ), + SliverMainAxisGroup( + key: centerKey, + slivers: [ + SliverToBoxAdapter(child: SizedBox(height: 50, child: Text('1'))), + SliverToBoxAdapter(child: SizedBox(height: 50, child: Text('2'))), + ], + ), + ], + ), + ), + ); + controller.jumpTo(-51); + await tester.pumpAndSettle(); + expect(tester.getTopLeft(find.text('-1')), const Offset(0, 1)); + expect(tester.getTopLeft(find.text('1')), const Offset(0, 51)); + expect(tester.getTopLeft(find.text('2')), const Offset(0, 101)); + expect(tester.getTopLeft(find.text('-2')), const Offset(0, -49)); + }); } Widget _buildSliverList({