Skip to content

fix(ObserverController): correct the scrolling clamped by an outdated scrollExtent - #151

Merged
LinXunFeng merged 1 commit into
mainfrom
fix/scroll-to-index-outdated-scroll-extent
Aug 22, 2026
Merged

fix(ObserverController): correct the scrolling clamped by an outdated scrollExtent#151
LinXunFeng merged 1 commit into
mainfrom
fix/scroll-to-index-outdated-scroll-extent

Conversation

@LinXunFeng

Copy link
Copy Markdown
Member

Related Issues

Description

Scrolling to an index right after the item count changed lands on the wrong
offset. Scrolling a second time works.

Root cause. SliverMultiBoxAdaptorElement.performRebuild() only updates
existing children. When the item count changes but every existing child rebuilds
into an identical widget, nothing calls markNeedsLayout(), so the sliver skips
its layout phase and SliverGeometry.scrollExtent keeps the value estimated for
the old item count. The framework guards against this only when _didUnderflow
is true, which is not the case here. maxScrollExtent is therefore outdated and
_calculateTargetLayoutOffset clamps the target offset to it:

[20 items]                  scrollExtent=1001.25 max=641.25
[40 items (after setState)] scrollExtent=1001.25 max=641.25   <- outdated
[after 1st jump]            scrollExtent=2007.5  max=1647.5 pixels=641.25
[after 2nd jump]            scrollExtent=2007.5  max=1647.5 pixels=1647.5

This also affects ListView, and only the paths that scroll once.
isFixedHeight: false goes through _handleScrollToIndex, which re-reads the
layout every round and recovers by itself.

Fix. The scrolling itself changes ScrollPosition.pixels, so the sliver is
laid out again and reports a fresh scrollExtent in the next frame. That is why
scrolling twice works, and the package can do the second one itself.

  • Expose isEnoughScroll from ObservePrepareScrollToIndexModel. It was already
    computed and thrown away, and means "the target offset has been clamped".
  • Add _scrollToWithCorrection(): while the scrolling was clamped, wait for the
    end of the frame, recalculate and scroll again, until the target offset no
    longer changes (or 5 corrections, or the render object is gone).
  • Apply it to the two one-shot paths: _handleScrollToIndexForFixedHeight() and
    the indexOffsetMap cache branch of _scrollToIndex().

Forcing a relayout up front instead would be smaller, but RenderSliverList
extrapolates its extent, so ListView.separated still lands 21~38px short on 4
of 24 item/separator height combinations. The loop converges on all of them and
only costs extra frames when the scrolling was actually clamped.

Tests

New group Scroll to index after the item count changed, one case in
grid_observer_test.dart and two in list_observer_test.dart (plain and
separated). Each asserts that maxScrollExtent is still outdated right after the
rebuild, so the tests fail loudly if the framework ever changes this behaviour.
Verified to fail without the fix.

  • flutter test - 83 passed
  • flutter analyze - no issues in lib/ and test/
  • dart format --set-exit-if-changed lib/ test/

… scrollExtent

A RenderSliverMultiBoxAdaptor may skip its layout phase when the number of
children changes but none of the existing children needs to be laid out again,
which leaves its SliverGeometry.scrollExtent outdated. The target offset is
then clamped to the outdated ScrollPosition.maxScrollExtent, so the target
child widget cannot be reached until scrolling to index a second time.

The scrolling itself changes ScrollPosition.pixels, which makes the sliver be
laid out again and report a fresh scrollExtent in the next frame. So expose
isEnoughScroll from ObservePrepareScrollToIndexModel to tell whether the
scrolling has been clamped, then recalculate and scroll again until the target
offset no longer changes.

It is applied to the two one-shot paths, the fixed height one and the one
hitting the indexOffsetMap cache. The path of gradually scrolling around the
target index location already converges by itself.

Closes #150
@LinXunFeng
LinXunFeng merged commit ad1003b into main Aug 22, 2026
2 checks passed
@LinXunFeng
LinXunFeng deleted the fix/scroll-to-index-outdated-scroll-extent branch August 22, 2026 07:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug report] GridView长度发生变化后,无法jumpTo新增的item

1 participant