diff --git a/packages/flutter/lib/src/rendering/sliver_group.dart b/packages/flutter/lib/src/rendering/sliver_group.dart index 4e99b0e75ea..feedabb3576 100644 --- a/packages/flutter/lib/src/rendering/sliver_group.dart +++ b/packages/flutter/lib/src/rendering/sliver_group.dart @@ -374,16 +374,22 @@ class RenderSliverMainAxisGroup extends RenderSliver // If the children's paint extent exceeds the remaining scroll extent of the `RenderSliverMainAxisGroup`, // they need to be corrected. if (paintOffset > remainingExtent) { + // Whether the current remaining space can accommodate all pinned children. + final bool pinnedChildrenOverflow = + maxScrollObstructionExtent > remainingExtent - constraints.overlap; final double paintCorrection = paintOffset - remainingExtent; paintOffset = remainingExtent; child = firstChild; while (child != null) { final SliverGeometry childLayoutGeometry = child.geometry!; - final bool childIsTooLarge = childLayoutGeometry.paintExtent > remainingExtent; - final bool pinnedHeadersOverflow = maxScrollObstructionExtent > remainingExtent; - final bool childIsPinnedHeader = childLayoutGeometry.maxScrollObstructionExtent > 0; - if (childIsTooLarge || (pinnedHeadersOverflow && childIsPinnedHeader)) { - final childParentData = child.parentData! as SliverPhysicalParentData; + final childParentData = child.parentData! as SliverPhysicalParentData; + final double childMainAxisPaintOffset = switch (constraints.axis) { + Axis.vertical => childParentData.paintOffset.dy, + Axis.horizontal => childParentData.paintOffset.dx, + }; + final double childPaintEnd = childMainAxisPaintOffset + childLayoutGeometry.paintExtent; + final bool childIsPinned = childLayoutGeometry.maxScrollObstructionExtent > 0; + if (childPaintEnd > remainingExtent || (pinnedChildrenOverflow && childIsPinned)) { childParentData.paintOffset = switch (constraints.axis) { Axis.vertical => Offset(0.0, childParentData.paintOffset.dy - paintCorrection), Axis.horizontal => Offset(childParentData.paintOffset.dx - paintCorrection, 0.0), 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 463050b760d..ecdb21ffff0 100644 --- a/packages/flutter/test/widgets/sliver_main_axis_group_test.dart +++ b/packages/flutter/test/widgets/sliver_main_axis_group_test.dart @@ -1518,6 +1518,69 @@ void main() { expect(semantics.nodesWith(label: 'b'), hasLength(1)); semantics.dispose(); }); + + testWidgets( + 'nested SliverMainAxisGroup with multiple PinnedHeaderSlivers positions correctly on scroll', + (WidgetTester tester) async { + final controller = ScrollController(); + addTearDown(controller.dispose); + final List keys = [GlobalKey(), GlobalKey(), GlobalKey(), GlobalKey()]; + + Future pumpWidget({bool reverse = false}) async { + return tester.pumpWidget( + _buildSliverMainAxisGroup( + controller: controller, + reverse: reverse, + viewportHeight: 300, + precedingSlivers: [ + PinnedHeaderSliver(child: SizedBox(height: 30, key: keys[0])), + ], + otherSlivers: [const SliverToBoxAdapter(child: SizedBox(height: 300))], + slivers: [ + PinnedHeaderSliver(child: SizedBox(height: 30, key: keys[1])), + SliverMainAxisGroup( + slivers: [ + PinnedHeaderSliver(child: SizedBox(height: 30, key: keys[2])), + const SliverToBoxAdapter(child: SizedBox(height: 30)), + PinnedHeaderSliver(child: SizedBox(height: 30, key: keys[3])), + const SliverToBoxAdapter(child: SizedBox(height: 30)), + ], + ), + const SliverToBoxAdapter(child: SizedBox(height: 30)), + ], + ), + ); + } + + Future verifyPositions(Map> offsetToPositions) async { + for (final MapEntry> entry in offsetToPositions.entries) { + controller.jumpTo(entry.key); + await tester.pumpAndSettle(); + for (var i = 0; i < keys.length; i++) { + expect(tester.getTopLeft(find.byKey(keys[i])).dy, entry.value[i]); + } + } + } + + // Forward direction + await pumpWidget(); + await verifyPositions({ + 10: [0.0, 30.0, 60.0, 110.0], + 40: [0.0, 30.0, 60.0, 90.0], + 70: [0.0, 30.0, 50.0, 80.0], + 100: [0.0, 30.0, 20.0, 50.0], + }); + + // Reverse direction + await pumpWidget(reverse: true); + await verifyPositions({ + 10: [270.0, 240.0, 210.0, 160.0], + 40: [270.0, 240.0, 210.0, 180.0], + 70: [270.0, 240.0, 220.0, 190.0], + 100: [270.0, 240.0, 250.0, 220.0], + }); + }, + ); } Widget _buildSliverList({ @@ -1559,6 +1622,7 @@ Widget _buildSliverMainAxisGroup({ Axis scrollDirection = Axis.vertical, bool reverse = false, List otherSlivers = const [], + List precedingSlivers = const [], }) { return MaterialApp( home: Directionality( @@ -1573,6 +1637,7 @@ Widget _buildSliverMainAxisGroup({ reverse: reverse, controller: controller, slivers: [ + ...precedingSlivers, SliverMainAxisGroup(slivers: slivers), ...otherSlivers, ],