From 8cc862c7270ef911eabb9aba932dca7ec7c33f0d Mon Sep 17 00:00:00 2001 From: Mairramer <50643541+Mairramer@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:23:07 -0300 Subject: [PATCH] Changes the offset computation to first item for RenderSliverMainAxisGroup (#154688) Fixes https://github.com/flutter/flutter/issues/154615 When scrolling back to the top, the position of the first visible item should have its scrollExtent subtracted. Otherwise, the accumulated position may be retained, which may result in the first visible item being hidden in some cases. --- .../lib/src/rendering/sliver_group.dart | 10 ++- .../widgets/sliver_main_axis_group_test.dart | 81 +++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/rendering/sliver_group.dart b/packages/flutter/lib/src/rendering/sliver_group.dart index b4edc7b0eb2..339021edd9a 100644 --- a/packages/flutter/lib/src/rendering/sliver_group.dart +++ b/packages/flutter/lib/src/rendering/sliver_group.dart @@ -6,6 +6,7 @@ library; import 'dart:math' as math; + import 'package:vector_math/vector_math_64.dart'; import 'object.dart'; @@ -213,7 +214,14 @@ class RenderSliverMainAxisGroup extends RenderSliver with ContainerRenderObjectM double childScrollOffset = 0.0; RenderSliver? current = childBefore(child as RenderSliver); while (current != null) { - childScrollOffset += current.geometry!.scrollExtent; + // If the current child is not the first child, then we need to + // add the scroll extent of the previous child to the current child's + // scroll offset. + if (childBefore(current) != null) { + childScrollOffset += childAfter(current)!.geometry!.scrollExtent + child.geometry!.scrollExtent; + } else if (!(childAfter(child) != null && current.geometry!.hasVisualOverflow)) { + childScrollOffset += current.geometry!.scrollExtent; + } current = childBefore(current); } return childScrollOffset; 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 e638dca79b2..7c9239972be 100644 --- a/packages/flutter/test/widgets/sliver_main_axis_group_test.dart +++ b/packages/flutter/test/widgets/sliver_main_axis_group_test.dart @@ -797,6 +797,64 @@ void main() { await tester.pumpAndSettle(); expect(tester.getTopLeft(find.byKey(key)), Offset.zero); }); + + testWidgets('SliverMainAxisGroup scrolls to the correct position when focusing on a text field within a header', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + addTearDown(controller.dispose); + final FocusNode textFieldFocus = FocusNode(); + addTearDown(textFieldFocus.dispose); + final FocusNode textFieldFocus2 = FocusNode(); + addTearDown(textFieldFocus2.dispose); + const ValueKey firstTextFieldKey = ValueKey(1); + + await tester.pumpWidget( + _buildSliverMainAxisGroup( + controller: controller, + slivers: [ + SliverPersistentHeader( + pinned: true, + delegate: _SliverTitleDelegate( + child: Container( + color: Colors.red, + height: 60.0, + ), + height: 60.0, + ), + ), + SliverToBoxAdapter( + child: Material( + child: TextField( + key: firstTextFieldKey, + focusNode: textFieldFocus, + ), + )), + SliverToBoxAdapter( + child: Container( + color: Colors.green, + height: 500, + ), + ), + SliverToBoxAdapter( + child: Material( + child: TextField( + focusNode: textFieldFocus2, + ), + )), + ], + ), + ); + + await tester.pumpAndSettle(); + await tester.pumpAndSettle(); + + textFieldFocus2.requestFocus(); + await tester.pumpAndSettle(); + + textFieldFocus.requestFocus(); + await tester.pumpAndSettle(); + + expect(tester.getTopLeft(find.byKey(firstTextFieldKey)), const Offset(0, 60)); + }); } Widget _buildSliverList({ @@ -875,3 +933,26 @@ class TestDelegate extends SliverPersistentHeaderDelegate { @override bool shouldRebuild(TestDelegate oldDelegate) => true; } + +class _SliverTitleDelegate extends SliverPersistentHeaderDelegate { + _SliverTitleDelegate({ + required this.height, + required this.child, + }); + final double height; + final Widget child; + + @override + double get minExtent => height; + @override + double get maxExtent => height; + + @override + Widget build( + BuildContext context, double shrinkOffset, bool overlapsContent) { + return child; + } + + @override + bool shouldRebuild(_SliverTitleDelegate oldDelegate) => true; +}