diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt index 754f10026624..2bed60a1e6a1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt @@ -36,6 +36,7 @@ import android.view.Menu import android.view.MenuItem import android.view.MotionEvent import android.view.View +import android.view.ViewConfiguration import android.view.ViewGroup import android.view.accessibility.AccessibilityNodeInfo import android.view.inputmethod.EditorInfo @@ -91,6 +92,7 @@ import com.facebook.react.views.text.internal.span.ReactStrikethroughSpan import com.facebook.react.views.text.internal.span.ReactTextPaintHolderSpan import com.facebook.react.views.text.internal.span.ReactUnderlineSpan import java.util.concurrent.CopyOnWriteArrayList +import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -132,6 +134,9 @@ public open class ReactEditText public constructor(context: Context) : AppCompat private var scrollWatcher: ScrollWatcher? private var keyListener: InternalKeyListener? = null private var detectScrollMovement = false + private var touchDownX = 0f + private var touchDownY = 0f + private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop private var onKeyPress = false private val textAttributes: TextAttributes private var typefaceDirty = false @@ -316,6 +321,8 @@ public open class ReactEditText public constructor(context: Context) : AppCompat override fun onTouchEvent(ev: MotionEvent): Boolean { when (ev.action) { MotionEvent.ACTION_DOWN -> { + touchDownX = ev.x + touchDownY = ev.y detectScrollMovement = true // Disallow parent views to intercept touch events, until we can detect if we should be // capturing these touches or not. @@ -324,21 +331,33 @@ public open class ReactEditText public constructor(context: Context) : AppCompat MotionEvent.ACTION_MOVE -> if (detectScrollMovement) { - if ( - !canScrollVertically(-1) && - !canScrollVertically(1) && - !canScrollHorizontally(-1) && - !canScrollHorizontally(1) - ) { - // We cannot scroll, let parent views take care of these touches. - this.parent.requestDisallowInterceptTouchEvent(false) + val deltaX = ev.x - touchDownX + val deltaY = ev.y - touchDownY + val dominantAxisDistance = max(abs(deltaX), abs(deltaY)) + if (dominantAxisDistance > touchSlop) { + if (!canScrollInGestureDirection(deltaX, deltaY)) { + // We cannot scroll, let parent views take care of these touches. + this.parent.requestDisallowInterceptTouchEvent(false) + } + detectScrollMovement = false } - detectScrollMovement = false } } return super.onTouchEvent(ev) } + private fun canScrollInGestureDirection(deltaX: Float, deltaY: Float): Boolean { + if (abs(deltaY) > abs(deltaX)) { + val canScrollUp = deltaY > 0 && canScrollVertically(-1) + val canScrollDown = deltaY <= 0 && canScrollVertically(1) + return canScrollUp || canScrollDown + } else { + val canScrollLeft = deltaX > 0 && canScrollHorizontally(-1) + val canScrollRight = deltaX <= 0 && canScrollHorizontally(1) + return canScrollLeft || canScrollRight + } + } + // Consume 'Enter' key events: TextView tries to give focus to the next TextInput, but it can't // since we only allow JS to change focus, which in turn causes TextView to crash. override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { diff --git a/packages/rn-tester/js/examples/ScrollView/ScrollViewExample.js b/packages/rn-tester/js/examples/ScrollView/ScrollViewExample.js index ef937f99089c..72580d9c0485 100644 --- a/packages/rn-tester/js/examples/ScrollView/ScrollViewExample.js +++ b/packages/rn-tester/js/examples/ScrollView/ScrollViewExample.js @@ -470,6 +470,24 @@ const examples: Array = [ return ; }, }, + { + name: 'scrollTextInputsVertical', + title: ' TextInputs\n', + description: + 'Vertical scroll gestures on a TextInput should scroll the ScrollView unless the input can also scroll vertically.', + render(): React.Node { + return ; + }, + }, + { + name: 'scrollTextInputsHorizontal', + title: ' TextInputs (horizontal = true)\n', + description: + 'Horizontal scroll gestures on a TextInput should scroll the ScrollView unless the input can also scroll horizontally.', + render(): React.Node { + return ; + }, + }, ]; if (Platform.OS === 'ios') { @@ -611,6 +629,145 @@ const ScrollsChildToFocusExample = () => { ); }; +const VerticalScrollTextInputs = () => { + const _verticalScrollView = + useRef>(null); + const [verticalScrolled, setVerticalScrolled] = useState(false); + + return ( + + + scrolled = {verticalScrolled ? 'true' : 'false'} + + { + setVerticalScrolled(event.nativeEvent.contentOffset.y > 0); + }}> + + + + + + + +