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; +}