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.
This commit is contained in:
Mairramer 2024-10-24 16:23:07 -03:00 committed by GitHub
parent 19296abf85
commit 8cc862c727
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 90 additions and 1 deletions

View File

@ -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;

View File

@ -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<int> firstTextFieldKey = ValueKey<int>(1);
await tester.pumpWidget(
_buildSliverMainAxisGroup(
controller: controller,
slivers: <Widget>[
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;
}