From c30a19cbe2b8faa08fbd8fedc2259b92a9d9a411 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 30 Jul 2026 12:43:49 +0200 Subject: [PATCH] [Android] Properly handle `requestDisallowInterceptTouchEvent` for v3 --- .../core/GestureHandlerOrchestrator.kt | 27 ++++++++++++++++++- .../RNGestureHandlerButtonViewManager.kt | 18 +++++++++++++ .../react/RNGestureHandlerDetectorView.kt | 22 +++++++++++++++ .../react/RNGestureHandlerRootHelper.kt | 17 +++--------- .../react/RNGestureHandlerRootView.kt | 10 ++++++- 5 files changed, 78 insertions(+), 16 deletions(-) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt index d496398b8a..b921d111f9 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt @@ -52,7 +52,8 @@ class GestureHandlerOrchestrator( // `contains` method on HashSet has O(1) complexity, so calling it inside for loop won't result in O(n^2) (contrary to ArrayList) private val awaitingHandlersTags = HashSet() - private var isHandlingTouch = false + var isHandlingTouch = false + private set private var handlingChangeSemaphore = 0 private var finishedHandlersCleanupScheduled = false private var activationIndex = 0 @@ -358,6 +359,30 @@ class GestureHandlerOrchestrator( event.recycle() } + /** + * Cancels all handlers created using API v1 and v2 + */ + fun cancelAllLegacyHandlers() { + val handlersToProcess = obtainHandlerList() + handlersToProcess.addAll(gestureHandlers) + + try { + handlersToProcess.forEach { + if (it.actionType == GestureHandler.ACTION_TYPE_JS_FUNCTION_OLD_API || + it.actionType == GestureHandler.ACTION_TYPE_JS_FUNCTION_NEW_API || + it.actionType == GestureHandler.ACTION_TYPE_REANIMATED_WORKLET || + it.actionType == GestureHandler.ACTION_TYPE_NATIVE_ANIMATED_EVENT + ) { + it.cancel() + } + } + + scheduleFinishedHandlersCleanup() + } finally { + recycleHandlerList(handlersToProcess) + } + } + /** * isViewAttachedUnderWrapper checks whether all of parents for view related to handler * view are attached. Since there might be an issue rarely observed when view diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt index 4c3b672fb2..a8f2f350a9 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt @@ -783,6 +783,24 @@ class RNGestureHandlerButtonViewManager : } } + override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { + super.requestDisallowInterceptTouchEvent(disallowIntercept) + if (!disallowIntercept) { + return + } + + val moduleId = moduleId ?: return + val managedHandlerTag = managedHandlerTag ?: return + + val orchestrator = + RNGestureHandlerRootView.findGestureHandlerRootView(this)?.orchestrator ?: return + val handler = RNGestureHandlerModule.registries[moduleId]?.getHandler(managedHandlerTag) ?: return + + if (!orchestrator.isHandlingTouch && handler.view != null) { + handler.cancel() + } + } + override fun onInterceptTouchEvent(event: MotionEvent): Boolean { if (super.onInterceptTouchEvent(event)) { return true diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt index d1126cbd8f..30ad5cea7f 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt @@ -46,6 +46,28 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) { super.onDetachedFromWindow() } + override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { + super.requestDisallowInterceptTouchEvent(disallowIntercept) + if (!disallowIntercept) { + return + } + + val orchestrator = RNGestureHandlerRootView.findGestureHandlerRootView(this)?.orchestrator ?: return + + if (!orchestrator.isHandlingTouch) { + val currentHandlers = attachedHandlers.mapNotNull { tag -> + RNGestureHandlerModule.registries[this.moduleId]?.getHandler(tag) + }.filter { + // The handler has been recorded + it.view != null + } + + currentHandlers.forEach { + it.cancel() + } + } + } + fun setModuleId(id: Int) { if (this.moduleId == id) { return diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt index 6485d0771f..57979ea310 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt @@ -16,7 +16,7 @@ import com.swmansion.gesturehandler.core.GestureHandlerOrchestrator import com.swmansion.gesturehandler.core.OnJSResponderCancelListener class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: ViewGroup, private val moduleId: Int) { - private val orchestrator: GestureHandlerOrchestrator? + val orchestrator: GestureHandlerOrchestrator? private val jsGestureHandler: GestureHandler? val rootView: ViewGroup private var shouldIntercept = false @@ -57,7 +57,7 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: } jsGestureHandler = RootViewGestureHandler(handlerTag = -wrappedViewTag) registry.registerHandler(jsGestureHandler) - registry.attachHandlerToView(jsGestureHandler.tag, wrappedViewTag, GestureHandler.ACTION_TYPE_JS_FUNCTION_OLD_API) + registry.attachHandlerToView(jsGestureHandler.tag, wrappedViewTag, GestureHandler.ACTION_TYPE_NONE) module.registerRootHelper(this) } @@ -120,7 +120,7 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: if (orchestrator != null && !passingTouch) { // if we are in the process of delivering touch events via GH orchestrator, we don't want to // treat it as a native gesture capturing the lock - tryCancelAllHandlers() + orchestrator.cancelAllLegacyHandlers() } } @@ -142,17 +142,6 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: return shouldIntercept } - private fun tryCancelAllHandlers() { - // In order to cancel handlers we activate handler that is hooked to the root view - jsGestureHandler?.apply { - if (state == GestureHandler.STATE_BEGAN) { - // Try activate main JS handler - activate() - end() - } - } - } - fun activateNativeHandlers(view: View) { orchestrator?.activateNativeHandlersForView(view) } diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt index 51427927b5..06c01bb8b5 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt @@ -12,6 +12,7 @@ import com.facebook.react.common.ReactConstants import com.facebook.react.uimanager.RootView import com.facebook.react.views.view.ReactViewGroup import com.swmansion.gesturehandler.core.GestureHandler +import com.swmansion.gesturehandler.core.GestureHandlerOrchestrator class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) { private var moduleId: Int = -1 @@ -19,6 +20,9 @@ class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) { private var unstableForceActive = false private var rootHelper: RNGestureHandlerRootHelper? = null // TODO: resettable lateinit + val orchestrator: GestureHandlerOrchestrator? + get() = rootHelper?.orchestrator + override fun onAttachedToWindow() { super.onAttachedToWindow() rootViewEnabled = unstableForceActive || !hasGestureHandlerEnabledRootView(this) @@ -72,7 +76,7 @@ class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) { } override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { - if (rootViewEnabled) { + if (rootViewEnabled && disallowIntercept) { rootHelper!!.requestDisallowInterceptTouchEvent() } super.requestDisallowInterceptTouchEvent(disallowIntercept) @@ -114,6 +118,10 @@ class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) { while (parent != null) { if (parent is RNGestureHandlerRootView) { gestureHandlerRootView = parent + + if (parent.rootViewEnabled) { + return parent + } } parent = parent.parent }