Repository navigation
Fix horizontal ScrollView resting past its left edge after a fling on Android 14 - #59020
Conversation
| } | ||
|
|
||
| if (pagingEnabled) { | ||
| if (scrollX <= 0 && correctedVelocityX < 0) { |
There was a problem hiding this comment.
Should we guard this specifically to Android 14?
There was a problem hiding this comment.
I'd rather guard on Android 12 than 14. From 12, HorizontalScrollView.fling() absorbs a fling into an edge the drag is still pulling, and the override skips that on every release since; 14 is just where skipping it strands the view, because its consumeFlingInStretch lacks the overscroll check 15 added. Below 12 there's no stretch to absorb into, so I've limited the branch to Build.VERSION_CODES.S and up in 9478215.
… 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().
The platform's fling absorbs into an edge the drag is still pulling from Android 12, the release that introduced the stretch; below it there is no stretch to absorb into, so React Native's own fling stays.
9478215 to
c1abf3c
Compare
|
@javache your question on an Android 14 guard is answered in its thread: 9478215 limits the left-edge |
|
@javache has imported this pull request. If you are a Meta employee, you can view this in D131751400. |
cortinico
left a comment
There was a problem hiding this comment.
Review automatically exported from Phabricator review in Meta.
|
This pull request was successfully merged by @YevheniiKotyrlo in 718b615 When will my fix make it into a release? | How to file a pick request? |


Summary:
On Android 14, a horizontal
ScrollViewresting 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'sHorizontalScrollView.fling()absorbs a fling into an edge effect that is still pulled.computeScroll()clamps the overscroll tomOverflingDistanceand springs back, but Android 14'sconsumeFlingInStretch()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'sHorizontalScrollViewhas noconsumeFlingInStretch.From Android 12, where the platform's fling absorbs into a pulled edge, 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 plainHorizontalScrollView. Every other fling, and every fling before Android 12, keeps React Native's own path. The verticalScrollViewis not affected:ScrollView.onTouchEventreleases throughflingWithNestedDispatch, which absorbs a fling into an edge the view rests at and never callsfling().Changelog:
[ANDROID] [FIXED] - Fix horizontal ScrollView resting past its left edge after a fling on Android 14
Test Plan:
ReactHorizontalScrollViewFlingTest(Robolectric,sdk = 34) drags a horizontal ScrollView at its left edge further left and releases it. Onmainit rests atscrollX = -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'sShadowEdgeEffectturns edge effects off on API 31+, so the test supplies a shadow that keeps the stretch a device uses.ReactHorizontalScrollViewFlingTest,ReactScrollViewTouchCancelTestandReactScrollViewPropertyTestpass under./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest -PenableWarningsAsErrors=true(16 tests), and ktfmt, run with the flagsscripts/format-kotlin.jspasses it, leaves both files unchanged.Item 0rests 18 px right of where it sat without this change and where it sat with it — before/after.