Skip to content

Commit 7974af7

Browse files
jasozhmeta-codesync[bot]
authored andcommitted
Fix TextInput breaking nested scroll gestures on Android (#58757)
Summary: Pull Request resolved: #58757 Fixes an Android-only bug where TextInputs can interfere with nested scroll gestures, causing scroll events to be performed on the outer parent instead of the inner ScrollView. ### Root Cause This issue occurs when you have a TextInput inside an inner ScrollView with `nestedScrollEnabled = true` inside an outer ScrollView. When a scroll gesture is performed on a TextInput that can scroll, the TextInput intercepts the gesture as expected. However, the next scroll gesture applied anywhere in the inner ScrollView scrolls the outer parent instead of the inner ScrollView. `inner.onInterceptTouchEvent()` calls `startNestedScroll()` on ACTION_DOWN and `stopNestedScroll()` on ACTION_UP. When the scroll gesture is on a scrollable TextInput, ReactEditText disallows parent views from intercepting touch events. This means on ACTION_UP, `inner.onInterceptTouchEvent()` and `stopNestedScroll()` is not triggered. Without `stopNestedScroll()`, the subsequent `startNestedScroll()` on the next ACTION_DOWN becomes a no-op, and the outer ScrollView intercepts the scroll gesture instead of the inner ScrollView. ### This PR Add a defensive `stopNestedScroll()` in `dispatchTouchEvent()` for ScrollViews. This matches the existing pattern already used by Android in `View.dispatchTouchEvent()` which includes the exact same defensive cleanup. Changelog: [Android][Fixed] - Prevent TextInputs from interfering with nested scroll gestures Reviewed By: festus14 Differential Revision: D122404118 fbshipit-source-id: 72909f0ab38298b28386e3a328db86732b2fd80a
1 parent 089b690 commit 7974af7

4 files changed

Lines changed: 27 additions & 1 deletion

File tree

‎packages/react-native/ReactAndroid/api/ReactAndroid.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5478,6 +5478,7 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android
54785478
public fun arrowScroll (I)Z
54795479
public fun canScrollHorizontally (I)Z
54805480
public fun dispatchGenericMotionEvent (Landroid/view/MotionEvent;)Z
5481+
public fun dispatchTouchEvent (Landroid/view/MotionEvent;)Z
54815482
public fun draw (Landroid/graphics/Canvas;)V
54825483
public fun executeKeyEvent (Landroid/view/KeyEvent;)Z
54835484
public fun flashScrollIndicators ()V
@@ -5626,6 +5627,7 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc
56265627
public synthetic fun <init> (Landroid/content/Context;Lcom/facebook/react/views/scroll/FpsListener;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
56275628
public fun abortAnimation ()V
56285629
public fun dispatchGenericMotionEvent (Landroid/view/MotionEvent;)Z
5630+
public fun dispatchTouchEvent (Landroid/view/MotionEvent;)Z
56295631
public fun draw (Landroid/graphics/Canvas;)V
56305632
public fun executeKeyEvent (Landroid/view/KeyEvent;)Z
56315633
public fun flashScrollIndicators ()V

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.kt‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,14 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) :
645645
override fun shouldDelayChildPressedState(): Boolean =
646646
hasChildPressedStateDelay ?: super.shouldDelayChildPressedState()
647647

648+
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
649+
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
650+
// Defensive cleanup for new gesture
651+
stopNestedScroll()
652+
}
653+
return super.dispatchTouchEvent(ev)
654+
}
655+
648656
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
649657
if (!scrollEnabled) return false
650658

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactNestedScrollView.kt‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<dfa41f479803f71200142269d679b3a1>>
7+
* @generated SignedSource<<5d7adbc00208fc9995b1c3e69c677a38>>
88
*/
99

1010
/**
@@ -565,6 +565,14 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) :
565565
override fun shouldDelayChildPressedState(): Boolean =
566566
hasChildPressedStateDelay ?: super.shouldDelayChildPressedState()
567567

568+
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
569+
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
570+
// Defensive cleanup for new gesture
571+
stopNestedScroll()
572+
}
573+
return super.dispatchTouchEvent(ev)
574+
}
575+
568576
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
569577
if (!scrollEnabled) return false
570578
if (!PointerEvents.canChildrenBeTouchTarget(pointerEvents)) return true

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.kt‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,14 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) :
557557
override fun shouldDelayChildPressedState(): Boolean =
558558
hasChildPressedStateDelay ?: super.shouldDelayChildPressedState()
559559

560+
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
561+
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
562+
// Defensive cleanup for new gesture
563+
stopNestedScroll()
564+
}
565+
return super.dispatchTouchEvent(ev)
566+
}
567+
560568
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
561569
if (!scrollEnabled) return false
562570
if (!PointerEvents.canChildrenBeTouchTarget(pointerEvents)) return true

0 commit comments

Comments
 (0)