Skip to content

Commit f0604b0

Browse files
authored
[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: <details> <summary>Test code</summary> ```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 ( <View style={styles.webViewContainer}> <GestureDetector gesture={g}> <WebView source={{ uri: 'https://templates.tiptap.dev/nw6Cmz6HfD' }} style={styles.webView} javaScriptEnabled={true} domStorageEnabled={true} startInLoadingState={true} onError={(syntheticEvent) => { const { nativeEvent } = syntheticEvent; console.warn('WebView error: ', nativeEvent); }} /> </GestureDetector> </View> ); } export default function BuggyApp() { return ( <GestureHandlerRootView> <WebViewScreen /> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, text: { fontSize: 18, }, webView: { flex: 1, }, webViewContainer: { flex: 1, width: '100%', }, }); ``` </details>
1 parent 3d137ff commit f0604b0

File tree

1 file changed

+20
-5
lines changed

1 file changed

+20
-5
lines changed

android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.widget.ScrollView
99
import com.facebook.react.views.scroll.ReactScrollView
1010
import com.facebook.react.views.swiperefresh.ReactSwipeRefreshLayout
1111
import com.facebook.react.views.textinput.ReactEditText
12+
import com.facebook.react.views.view.ReactViewGroup
1213
import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager
1314
import com.swmansion.gesturehandler.react.isScreenReaderOn
1415

@@ -79,6 +80,7 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
7980
is ReactEditText -> this.hook = EditTextHook(this, view)
8081
is ReactSwipeRefreshLayout -> this.hook = SwipeRefreshLayoutHook(this, view)
8182
is ReactScrollView -> this.hook = ScrollViewHook()
83+
is ReactViewGroup -> this.hook = ReactViewGroupHook()
8284
}
8385
}
8486

@@ -99,7 +101,7 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
99101
if (state == STATE_UNDETERMINED && !hook.canBegin(event)) {
100102
cancel()
101103
} else {
102-
view.onTouchEvent(event)
104+
hook.sendTouchEvent(view, event)
103105
if ((state == STATE_UNDETERMINED || state == STATE_BEGAN) && view.isPressed) {
104106
activate()
105107
}
@@ -116,12 +118,12 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
116118
when {
117119
shouldActivateOnStart -> {
118120
tryIntercept(view, event)
119-
view.onTouchEvent(event)
121+
hook.sendTouchEvent(view, event)
120122
activate()
121123
}
122124

123125
tryIntercept(view, event) -> {
124-
view.onTouchEvent(event)
126+
hook.sendTouchEvent(view, event)
125127
activate()
126128
}
127129

@@ -136,7 +138,7 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
136138
}
137139
}
138140
} else if (state == STATE_ACTIVE) {
139-
view.onTouchEvent(event)
141+
hook.sendTouchEvent(view, event)
140142
}
141143
}
142144

@@ -145,7 +147,7 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
145147
val event = MotionEvent.obtain(time, time, MotionEvent.ACTION_CANCEL, 0f, 0f, 0).apply {
146148
action = MotionEvent.ACTION_CANCEL
147149
}
148-
view!!.onTouchEvent(event)
150+
hook.sendTouchEvent(view, event)
149151
event.recycle()
150152
}
151153

@@ -199,6 +201,11 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
199201
* by this one.
200202
*/
201203
fun shouldCancelRootViewGestureHandlerIfNecessary() = false
204+
205+
/**
206+
* Passes the event down to the underlying view using the correct method.
207+
*/
208+
fun sendTouchEvent(view: View?, event: MotionEvent) = view?.onTouchEvent(event)
202209
}
203210

204211
private class EditTextHook(
@@ -278,4 +285,12 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
278285
private class ScrollViewHook : NativeViewGestureHandlerHook {
279286
override fun shouldCancelRootViewGestureHandlerIfNecessary() = true
280287
}
288+
289+
private class ReactViewGroupHook : NativeViewGestureHandlerHook {
290+
// There are cases where a native component is wrapped with a `ReactViewGroup` (the component is rendered
291+
// inside a `<View />` component in JS). In such cases, calling `onTouchEvent` wouldn't work as those are
292+
// ignored by the wrapper view. Instead `dispatchTouchEvent` can be used, which causes the view to dispatch
293+
// the event to its children.
294+
override fun sendTouchEvent(view: View?, event: MotionEvent) = view?.dispatchTouchEvent(event)
295+
}
281296
}

0 commit comments

Comments
 (0)