diff --git a/apps/common-app/src/new_api/tests/nestedTouchables/index.tsx b/apps/common-app/src/new_api/tests/nestedTouchables/index.tsx index dd3258ae54..72da4e2c31 100644 --- a/apps/common-app/src/new_api/tests/nestedTouchables/index.tsx +++ b/apps/common-app/src/new_api/tests/nestedTouchables/index.tsx @@ -12,6 +12,7 @@ export default function NestedTouchablesExample() { const [log, setLog] = useState([]); const pushLog = (message: string) => { + console.log(message); setLog((prev) => [...prev, `[${new Date().toLocaleTimeString()}] ${message}`].slice(-6) ); @@ -20,11 +21,13 @@ export default function NestedTouchablesExample() { const outerTap = useTapGesture({ runOnJS: true, onActivate: () => pushLog('outer tap gesture'), + testID: 'outer-tap', }); const innerTap = useTapGesture({ runOnJS: true, onActivate: () => pushLog('inner tap gesture'), + testID: 'inner-tap', }); return ( @@ -40,6 +43,11 @@ export default function NestedTouchablesExample() { pushLog('outer press in')} + onPressOut={() => pushLog('outer press out')} + onLongPress={() => pushLog('outer long press')} onPress={() => pushLog('outer Touchable')}> Outer Touchable @@ -49,6 +57,11 @@ export default function NestedTouchablesExample() { pushLog('inner press in')} + onPressOut={() => pushLog('inner press out')} + onLongPress={() => pushLog('inner long press')} onPress={() => pushLog('inner Touchable')}> Inner Touchable diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt index 995146da5a..2042529e2d 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt @@ -693,6 +693,8 @@ open class GestureHandler { return interactionController?.shouldHandlerBeCancelledBy(this, handler) ?: false } + open fun shouldBeginWithRecordedHandlers(recorded: List): Boolean = true + fun isWithinBounds(view: View?, posX: Float, posY: Float): Boolean { if (RNSVGHitTester.isSvgElement(view!!)) { return RNSVGHitTester.hitTest(view, posX, posY) 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 8778e7bcab..21fe9d94fb 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 @@ -448,11 +448,16 @@ class GestureHandlerOrchestrator( return } - gestureHandlers.add(handler) handler.isActive = false handler.isAwaiting = false handler.activationIndex = Int.MAX_VALUE handler.prepare(view, this) + + if (!handler.shouldBeginWithRecordedHandlers(gestureHandlers)) { + handler.cancel() + } + + gestureHandlers.add(handler) } private fun isViewOverflowingParent(view: View): Boolean { diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt index 73e0262c08..39cef22bf9 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt @@ -83,6 +83,9 @@ class NativeViewGestureHandler : GestureHandler() { override fun shouldBeCancelledBy(handler: GestureHandler): Boolean = !disallowInterruption + override fun shouldBeginWithRecordedHandlers(recorded: List): Boolean = + hook.shouldBeginWithRecordedHandlers(recorded, this) + override fun onPrepare() { when (val view = view) { is NativeViewGestureHandlerHook -> this.hook = view @@ -271,6 +274,16 @@ class NativeViewGestureHandler : GestureHandler() { */ fun shouldCancelRootViewGestureHandlerIfNecessary() = false + /** + * Called when the handler is being recorded by the orchestrator, before any pointer events + * are delivered. Returning `false` cancels the handler immediately. + * + * @param recorded handlers already recorded for the current touch + * @param handler the handler being recorded + */ + fun shouldBeginWithRecordedHandlers(recorded: List, handler: NativeViewGestureHandler): Boolean = + true + /** * Passes the event down to the underlying view using the correct method. */ 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 39c59da6bc..1145ddf3b3 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 @@ -42,6 +42,8 @@ import com.facebook.react.uimanager.style.BorderStyle import com.facebook.react.uimanager.style.LogicalEdge import com.facebook.react.viewmanagers.RNGestureHandlerButtonManagerDelegate import com.facebook.react.viewmanagers.RNGestureHandlerButtonManagerInterface +import com.swmansion.gesturehandler.core.GestureHandler +import com.swmansion.gesturehandler.core.HoverGestureHandler import com.swmansion.gesturehandler.core.NativeViewGestureHandler import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager.ButtonViewGroup @@ -738,6 +740,16 @@ class RNGestureHandlerButtonViewManager : isTouched = false } + override fun shouldBeginWithRecordedHandlers( + recorded: List, + handler: NativeViewGestureHandler, + ): Boolean = recorded.all { + it.shouldRecognizeSimultaneously(handler) || + handler.shouldRecognizeSimultaneously(it) || + it.view == this || + it is HoverGestureHandler + } + private fun tryFreeingResponder() { if (touchResponder === this) { touchResponder = null