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)
+ }
}