From f0604b03995ce06ac9e0a10d00ef9802dd5e1e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bert?= <63123542+m-bert@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:47:17 +0100 Subject: [PATCH] [Android] Change `onTouchEvent` to `dispatchTouchEvent` in `NativeViewGestureHandler` (#3244) ## Description Currently wrapping `WebView` into `NativeViewGestureHandler` doesn't work as expected - events from `native` gesture do not reach `WebView`. This happens because inside `NativeViewGestureHandler` we call `onTouchEvent` method. In native hierarchy `WebView` is nested inside `ViewGroup`, which means that calling `onTouchEvent` instead of `dispatchTouchEvent` won't do anything to the `WebView`. Should fix #2454 Fixes #3196 ## Test plan Tested on example app and the code below:
Test code ```jsx import * as React from 'react'; import { View, StyleSheet } from 'react-native'; import { WebView } from 'react-native-webview'; import { Gesture, GestureDetector, GestureHandlerRootView, } from 'react-native-gesture-handler'; function WebViewScreen() { const native = Gesture.Native() .shouldActivateOnStart(true) .disallowInterruption(true); const pan = Gesture.Pan().onChange(console.log); const g = Gesture.Simultaneous(native, pan); return ( { const { nativeEvent } = syntheticEvent; console.warn('WebView error: ', nativeEvent); }} /> ); } export default function BuggyApp() { return ( ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, text: { fontSize: 18, }, webView: { flex: 1, }, webViewContainer: { flex: 1, width: '100%', }, }); ```
--- .../core/NativeViewGestureHandler.kt | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt index 38e75e1a8b..36a31948d7 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt @@ -9,6 +9,7 @@ import android.widget.ScrollView import com.facebook.react.views.scroll.ReactScrollView import com.facebook.react.views.swiperefresh.ReactSwipeRefreshLayout import com.facebook.react.views.textinput.ReactEditText +import com.facebook.react.views.view.ReactViewGroup import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager import com.swmansion.gesturehandler.react.isScreenReaderOn @@ -79,6 +80,7 @@ class NativeViewGestureHandler : GestureHandler() { is ReactEditText -> this.hook = EditTextHook(this, view) is ReactSwipeRefreshLayout -> this.hook = SwipeRefreshLayoutHook(this, view) is ReactScrollView -> this.hook = ScrollViewHook() + is ReactViewGroup -> this.hook = ReactViewGroupHook() } } @@ -99,7 +101,7 @@ class NativeViewGestureHandler : GestureHandler() { if (state == STATE_UNDETERMINED && !hook.canBegin(event)) { cancel() } else { - view.onTouchEvent(event) + hook.sendTouchEvent(view, event) if ((state == STATE_UNDETERMINED || state == STATE_BEGAN) && view.isPressed) { activate() } @@ -116,12 +118,12 @@ class NativeViewGestureHandler : GestureHandler() { when { shouldActivateOnStart -> { tryIntercept(view, event) - view.onTouchEvent(event) + hook.sendTouchEvent(view, event) activate() } tryIntercept(view, event) -> { - view.onTouchEvent(event) + hook.sendTouchEvent(view, event) activate() } @@ -136,7 +138,7 @@ class NativeViewGestureHandler : GestureHandler() { } } } else if (state == STATE_ACTIVE) { - view.onTouchEvent(event) + hook.sendTouchEvent(view, event) } } @@ -145,7 +147,7 @@ class NativeViewGestureHandler : GestureHandler() { val event = MotionEvent.obtain(time, time, MotionEvent.ACTION_CANCEL, 0f, 0f, 0).apply { action = MotionEvent.ACTION_CANCEL } - view!!.onTouchEvent(event) + hook.sendTouchEvent(view, event) event.recycle() } @@ -199,6 +201,11 @@ class NativeViewGestureHandler : GestureHandler() { * by this one. */ fun shouldCancelRootViewGestureHandlerIfNecessary() = false + + /** + * Passes the event down to the underlying view using the correct method. + */ + fun sendTouchEvent(view: View?, event: MotionEvent) = view?.onTouchEvent(event) } private class EditTextHook( @@ -278,4 +285,12 @@ class NativeViewGestureHandler : GestureHandler() { private class ScrollViewHook : NativeViewGestureHandlerHook { override fun shouldCancelRootViewGestureHandlerIfNecessary() = true } + + private class ReactViewGroupHook : NativeViewGestureHandlerHook { + // There are cases where a native component is wrapped with a `ReactViewGroup` (the component is rendered + // inside a `` component in JS). In such cases, calling `onTouchEvent` wouldn't work as those are + // ignored by the wrapper view. Instead `dispatchTouchEvent` can be used, which causes the view to dispatch + // the event to its children. + override fun sendTouchEvent(view: View?, event: MotionEvent) = view?.dispatchTouchEvent(event) + } }