Skip to content

Fix horizontal ScrollView resting past its left edge after a fling on Android 14 - #59020

Open
YevheniiKotyrlo wants to merge 1 commit into
react:mainfrom
YevheniiKotyrlo:fix/android-horizontal-fling-into-pulled-edge
Open

YevheniiKotyrlo wants to merge 1 commit into
react:mainfrom
YevheniiKotyrlo:fix/android-horizontal-fling-into-pulled-edge

Conversation

@YevheniiKotyrlo

@YevheniiKotyrlo YevheniiKotyrlo commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary:

On Android 14, a horizontal ScrollView resting at its left edge, dragged further left and released while moving, comes to rest 6 dp past that edge and stays there until it is touched again: the content shows a gap and the view reports a negative offset. In right-to-left layout the left edge is the reading end, so a list flung toward its end sticks the same way.

ReactHorizontalScrollView.fling() always starts its own overscroll fling (minX = 0, overX = width / 2), where the platform's HorizontalScrollView.fling() absorbs a fling into an edge effect that is still pulled. computeScroll() clamps the overscroll to mOverflingDistance and springs back, but Android 14's consumeFlingInStretch() feeds every spring-back step into the stretch the drag is still pulling, so the scroller finishes at 0 and the view stays at −6 dp. Android 15 adds the guard that prevents it (if (scrollX < 0 || scrollX > getScrollRange()) return unconsumed;), and Android 13's HorizontalScrollView has no consumeFlingInStretch.

This hands a fling that heads into the left edge the view rests at to super.fling(), so the platform absorbs it into the edge effect as it does for a plain HorizontalScrollView. Every other fling keeps React Native's own path. The vertical ScrollView is not affected: ScrollView.onTouchEvent releases through flingWithNestedDispatch, which absorbs a fling into an edge the view rests at and never calls fling().

Changelog:

[ANDROID] [FIXED] - Fix horizontal ScrollView resting past its left edge after a fling on Android 14

Test Plan:

  • New ReactHorizontalScrollViewFlingTest (Robolectric, sdk = 34) drags a horizontal ScrollView at its left edge further left and releases it. On main it rests at scrollX = -6 (expected:<[0]> but was:<[-6]>); with this change at 0. A second case flings into the content, so the drag is shown to produce a real fling. Robolectric's ShadowEdgeEffect turns edge effects off on API 31+, so the test supplies a shadow that keeps the stretch a device uses.
  • ./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest: 707 tests pass. yarn format-check-kotlin and yarn format-check-java pass.
  • RNTester on an Android 14 (API 34) emulator: after the same fling, Item 0 rests 18 px right of where it sat without this change and where it sat with it — before/after.

… Android 14

A horizontal ScrollView resting at its left edge, dragged further left and
released while moving, came to rest 6 dp past that edge on Android 14.
ReactHorizontalScrollView.fling() starts its own overscroll fling where the
platform absorbs a fling into the edge effect the drag is still pulling, and
Android 14's consumeFlingInStretch() feeds the spring-back into that stretch,
so the view never moves back.

Hand a fling into the left edge the view rests at to super.fling().
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 12, 2026
@YevheniiKotyrlo

Copy link
Copy Markdown
Contributor Author

Device evidence — before / after

UNFIXED — flung toward its left edge from rest, the strip stops 6 dp past that edge and stays there.

FIXED — the same fling leaves the strip at its edge.

before — main after — with this PR

RNTester's ScrollViewExample/horizontalScrollTo on an Android 14 (API 34) emulator, 1140×2400 at density 480, built from the same commit with and without this change. After a fast swipe to the right from rest, Item 0's left edge reads 48 px without the change and 30 px with it, where it sat at rest, at 3 s and again at 6 s.

@YevheniiKotyrlo
YevheniiKotyrlo marked this pull request as ready for review October 12, 2026 23:10
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Oct 12, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant