From 8c41b09630e44b12982e76caf2ced00f5403b3ee Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Mon, 18 Sep 2023 11:04:28 +0200 Subject: [PATCH] Improve multi-pointer behavior (with ScrollView) on Android (#2551) ## Description This PR solves two problems: - `ScrollView` wrapped with `NativeViewGestureHandler` wasn't canceling the root view handler, which in turn meant that [`shouldIntercept` flag was never set](https://github.com/software-mansion/react-native-gesture-handler/blob/355a82fd3cf39b1bd4a57af958f040b563a1823e/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt#L71) and event was [dispatched to both, the handler and the view](https://github.com/software-mansion/react-native-gesture-handler/blob/355a82fd3cf39b1bd4a57af958f040b563a1823e/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt#L35-L37). - After a handler was activated and the root handler was canceled (setting `shouldIntercept` to true), and a new pointer was added to the screen, the root handler would begin and set `shouldIntercept` back to false. Again, this meant dispatching events both to the handler and the view. Fixes https://github.com/software-mansion/react-native-gesture-handler/issues/1679 ## Test plan Tested on the Example app and [reproduction from the issue](https://github.com/software-mansion/react-native-gesture-handler/issues/1679#issuecomment-1471576215) --- .../gesturehandler/core/GestureHandlerOrchestrator.kt | 2 ++ .../gesturehandler/core/NativeViewGestureHandler.kt | 6 ++++++ .../gesturehandler/react/RNGestureHandlerRootHelper.kt | 4 +++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt b/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt index 2c6c277787..d44513339d 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt @@ -325,6 +325,8 @@ class GestureHandlerOrchestrator( return parent === wrapperView } + fun isAnyHandlerActive() = gestureHandlers.any { it?.state == GestureHandler.STATE_ACTIVE } + /** * Transforms an event in the coordinates of wrapperView into the coordinate space of the received view. * 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 3fd219d041..64f230a2c8 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewConfiguration import android.view.ViewGroup 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 @@ -75,6 +76,7 @@ class NativeViewGestureHandler : GestureHandler() { is NativeViewGestureHandlerHook -> this.hook = view is ReactEditText -> this.hook = EditTextHook(this, view) is ReactSwipeRefreshLayout -> this.hook = SwipeRefreshLayoutHook(this, view) + is ReactScrollView -> this.hook = ScrollViewHook() } } @@ -248,4 +250,8 @@ class NativeViewGestureHandler : GestureHandler() { // oh well ¯\_(ツ)_/¯ } } + + private class ScrollViewHook : NativeViewGestureHandlerHook { + override fun shouldCancelRootViewGestureHandlerIfNecessary() = true + } } diff --git a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt index b20ba42229..f6ea6a107f 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt @@ -59,7 +59,9 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: private inner class RootViewGestureHandler : GestureHandler() { override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) { val currentState = state - if (currentState == STATE_UNDETERMINED) { + // we shouldn't stop intercepting events when there is an active handler already, which could happen when + // adding a new pointer to the screen after a handler activates + if (currentState == STATE_UNDETERMINED && (!shouldIntercept || orchestrator?.isAnyHandlerActive() != true)) { begin() shouldIntercept = false }