Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit de20060

Browse files
committedJan 29, 2025
sticky_header: Fix _findChildAtEnd when viewport partly consumed already
In particular this will affect the upper sliver (with older messages) in the message list, when we start using two slivers there in earnest.
1 parent 5022670 commit de20060

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed
 

‎lib/widgets/sticky_header.dart

+4-1
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,10 @@ class _RenderSliverStickyHeaderListInner extends RenderSliverList {
733733
///
734734
/// This means (child start) < (viewport end) <= (child end).
735735
RenderBox? _findChildAtEnd() {
736-
final endOffset = constraints.scrollOffset + constraints.viewportMainAxisExtent;
736+
/// The end of the visible area available to this sliver,
737+
/// in this sliver's "scroll offset" coordinates.
738+
final endOffset = constraints.scrollOffset
739+
+ constraints.remainingPaintExtent;
737740

738741
RenderBox? child;
739742
for (child = lastChild; ; child = childBefore(child)) {

‎test/widgets/sticky_header_test.dart

+64
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,70 @@ void main() {
153153
// will get placed at zero rather than properly extend up off screen.
154154
check(tester.getTopLeft(find.text("Item 0"))).equals(Offset(0, -200));
155155
});
156+
157+
testWidgets('sliver only part of viewport, header at end', (tester) async {
158+
const centerKey = ValueKey('center');
159+
final controller = ScrollController();
160+
await tester.pumpWidget(Directionality(textDirection: TextDirection.ltr,
161+
child: CustomScrollView(
162+
controller: controller,
163+
anchor: 0.5,
164+
center: centerKey,
165+
slivers: [
166+
SliverStickyHeaderList(
167+
headerPlacement: HeaderPlacement.scrollingStart,
168+
delegate: SliverChildListDelegate(
169+
List.generate(100, (i) => StickyHeaderItem(
170+
header: _Header(99 - i, height: 20),
171+
child: _Item(99 - i, height: 100))))),
172+
SliverStickyHeaderList(
173+
key: centerKey,
174+
headerPlacement: HeaderPlacement.scrollingStart,
175+
delegate: SliverChildListDelegate(
176+
List.generate(100, (i) => StickyHeaderItem(
177+
header: _Header(100 + i, height: 20),
178+
child: _Item(100 + i, height: 100))))),
179+
])));
180+
181+
final overallSize = tester.getSize(find.byType(CustomScrollView));
182+
final extent = overallSize.onAxis(Axis.vertical);
183+
assert(extent == 600);
184+
185+
void checkState(int index, {required double item, required double header}) {
186+
final itemElement = tester.firstElement(find.byElementPredicate((element) {
187+
if (element.widget is! _Item) return false;
188+
final renderObject = element.renderObject as RenderBox;
189+
return (renderObject.size.contains(renderObject.globalToLocal(
190+
Offset(overallSize.width / 2, 1)
191+
)));
192+
}));
193+
final itemWidget = itemElement.widget as _Item;
194+
check(itemWidget.index).equals(index);
195+
// TODO the `.first` calls should be unnecessary; that's another bug
196+
// check(_headerIndex(tester)).equals(index);
197+
check(tester.widget<_Header>(find.byType(_Header).first).index)
198+
.equals(index);
199+
check((itemElement.renderObject as RenderBox).localToGlobal(Offset(0, 0)))
200+
.equals(Offset(0, item));
201+
check(tester.getTopLeft(find.byType(_Header).first))
202+
.equals(Offset(0, header));
203+
}
204+
205+
check(controller.offset).equals(0);
206+
checkState( 97, item: 0, header: 0);
207+
208+
controller.jumpTo(-5);
209+
await tester.pump();
210+
checkState( 96, item: -95, header: -15);
211+
212+
controller.jumpTo(-600);
213+
await tester.pump();
214+
checkState( 91, item: 0, header: 0);
215+
216+
controller.jumpTo(600);
217+
await tester.pump();
218+
checkState(103, item: 0, header: 0);
219+
});
156220
}
157221

158222
Future<void> _checkSequence(

0 commit comments

Comments
 (0)
Please sign in to comment.