fix(layout): recompute the tail of horizontal lists on the scroll axis - #2496
Open
ahmdshrif wants to merge 1 commit into
Open
fix(layout): recompute the tail of horizontal lists on the scroll axis#2496ahmdshrif wants to merge 1 commit into
ahmdshrif wants to merge 1 commit into
Conversation
_recomputeLayouts guards against a partial recompute leaving the tail behind by comparing the last item's position with the end of the range it just processed. It read y unconditionally, but horizontal linear layouts keep y at 0 for every item and advance x, so the comparison was always 0 < 0 and the guard never fired. The tail then kept positions derived from stale estimates: it overlapped the items that had just been moved, which breaks the binary searches in findVisibleIndex, and getLayoutSize() reported a content width taken from that stale last item. Compare on the scroll axis instead - x when horizontal, y otherwise. Grid and masonry lay out along y, so they are unaffected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
RVLayoutManager._recomputeLayoutscaps each recompute pass atmaxItemsToProcessitems. When the pass ends before the last item, it checks whether the items it just moved now sit past the untouched tail, and if so recomputes the tail too:That check reads
yunconditionally. In a horizontal listRVLinearLayoutManagerImpl.recomputeLayoutsassignslayout.y = 0for every item and advanceslayout.xinstead, so the comparison is always0 < 0and the tail is never recomputed. The guard is dead code on the horizontal path.Two things go wrong as a result:
xthey were given by the previous, smaller estimates, so they land on top of much earlier items. That corruptsfindFirstVisibleIndex/findLastVisibleIndex(both binary-search the layout array assuming it is monotonic on the scroll axis), so the wrong items are rendered and recycled.getLayoutSize()reports a stale content width.RVLinearLayoutManagerImpl.getLayoutSizederives the horizontal size fromlastLayout.x + lastLayout.width, which is exactly the value the guard exists to keep fresh. The list reports a content width several times smaller than the real one, which breaks scroll extent,scrollToEndand end-reached detection until a later layout pass happens to sweep the tail back in.On a 300-item horizontal list where the first 25 measured items come back wider than the estimate, the recompute pass covers 0–274 and then stops:
xxxThe equivalent vertical list is already correct today — this is purely the horizontal path.
Fix
Compare on the axis the list actually grows along:
horizontalis already the base class's own notion of the scroll axis, so no subclass needs to change. Grid and masonry both lay out alongy, so their behaviour is byte-identical.Reviewers' hat-rack 🎩
getScrollAxisPositionhelper is the only behavioural change; everything else is a comment update.LinearLayoutManager.test.tsgains aTail sync after a partial recomputeblock with a vertical and a horizontal case. The vertical one passes both before and after the fix — it is there to show the assertions describe existing intended behaviour, not new behaviour. The horizontal one fails onmainwithExpected: >= 274000 / Received: 55000and passes with the fix.tsc --noEmitandeslintclean.