From cf73d92b2a41f73b201a85fccde4e3985476dc72 Mon Sep 17 00:00:00 2001 From: Hugo Extrat Date: Mon, 17 Aug 2026 08:42:32 +0200 Subject: [PATCH] fix(Android): cancel button handlers when a native view takes the touch lock `Pressable` (and every component built on the native button) keeps its `NativeViewGestureHandler` running when a native view grabs the touch lock, because that handler is attached with `ACTION_TYPE_NONE` and `cancelAllLegacyHandlers` only cancelled the action-driven ones. The visible symptom is a phantom press: a finger put down on a list row to stop a fling makes the enclosing `ScrollView` claim the touch (Android's `ScrollView` intercepts the `ACTION_DOWN` while the scroller is running and calls `requestDisallowInterceptTouchEvent`), yet the button handler survives, ends on the finger lift and dispatches `onPress` for a row the user never meant to tap. Cancel `NativeViewGestureHandler`s attached with `ACTION_TYPE_NONE` too. The root view's own handler shares that action type but is not a `NativeViewGestureHandler`, so it keeps running and interception is unaffected. Renamed the method since it no longer cancels only the legacy handlers. --- .../core/GestureHandlerOrchestrator.kt | 15 ++++++++++++--- .../react/RNGestureHandlerRootHelper.kt | 2 +- 2 files changed, 13 insertions(+), 4 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 9ced951328..e021bdf90b 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 @@ -366,9 +366,17 @@ class GestureHandlerOrchestrator( } /** - * Cancels all handlers created using API v1 and v2 + * Cancels the handlers that lose to a native view taking the touch lock: the ones created using + * API v1 and v2, and the [NativeViewGestureHandler] a button manages for itself. + * + * The button's handler is attached with [GestureHandler.ACTION_TYPE_NONE] - it dispatches its + * events natively rather than through an action - so an action type check alone leaves it + * running. It has to be cancelled here as well, or a touch that a native view claims still ends + * the button handler and fires a press. The root view's own handler shares that action type and + * must keep running, hence the type check rather than a plain [GestureHandler.ACTION_TYPE_NONE] + * one. */ - fun cancelAllLegacyHandlers() { + fun cancelHandlersLosingToNativeGesture() { val handlersToProcess = obtainHandlerList() handlersToProcess.addAll(gestureHandlers) @@ -377,7 +385,8 @@ class GestureHandlerOrchestrator( 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.actionType == GestureHandler.ACTION_TYPE_NATIVE_ANIMATED_EVENT || + (it is NativeViewGestureHandler && it.actionType == GestureHandler.ACTION_TYPE_NONE) ) { it.cancel() } 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 57979ea310..11d73416af 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 @@ -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 - orchestrator.cancelAllLegacyHandlers() + orchestrator.cancelHandlersLosingToNativeGesture() } }