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..7545d402d3 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 @@ -93,6 +93,7 @@ class NativeViewGestureHandler : GestureHandler() { is ReactTextView -> this.hook = TextViewHook() is ReactViewGroup -> this.hook = ReactViewGroupHook() } + hook.attachHandler(this) } override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) { @@ -166,6 +167,7 @@ class NativeViewGestureHandler : GestureHandler() { override fun onFail() = dispatchCancelEventToView() override fun onReset() { + hook.detachHandler() this.hook = defaultHook lastActiveUpdate = null } @@ -275,6 +277,19 @@ class NativeViewGestureHandler : GestureHandler() { * Passes the event down to the underlying view using the correct method. */ fun sendTouchEvent(view: View?, event: MotionEvent) = view?.onTouchEvent(event) + + /** + * Called after the handler has prepared itself on this hook's view. The hook + * can retain the handler reference to drive state transitions from the view's + * own lifecycle (e.g. calling `activate()`, `fail()` or `cancel()` in + * response to native view state changes). + */ + fun attachHandler(handler: NativeViewGestureHandler) = Unit + + /** + * Called on handler reset so the hook can drop its retained handler reference. + */ + fun detachHandler() = Unit } private class TextViewHook : NativeViewGestureHandlerHook { 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 a8f9c5626d..83bfc664b7 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,7 @@ 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.NativeViewGestureHandler import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager.ButtonViewGroup @@ -380,6 +381,7 @@ class RNGestureHandlerButtonViewManager : private var pressInTimestamp = 0L private var pendingPressOut: Runnable? = null private var isPointerInsideBounds = false + private var attachedHandler: NativeViewGestureHandler? = null // When non-null the ripple is drawn in dispatchDraw (above background, below children). // When null the ripple lives on the foreground drawable instead. @@ -715,6 +717,14 @@ class RNGestureHandlerButtonViewManager : } } + override fun attachHandler(handler: NativeViewGestureHandler) { + attachedHandler = handler + } + + override fun detachHandler() { + attachedHandler = null + } + override fun canBegin(event: MotionEvent): Boolean { if (event.action == MotionEvent.ACTION_CANCEL || event.action == MotionEvent.ACTION_UP || @@ -816,6 +826,9 @@ class RNGestureHandlerButtonViewManager : super.setPressed(pressed) if (pressed) { + attachedHandler?.takeIf { + it.state == GestureHandler.STATE_UNDETERMINED || it.state == GestureHandler.STATE_BEGAN + }?.activate() animatePressIn() } else { animatePressOut() diff --git a/packages/react-native-gesture-handler/src/v3/components/Touchable/Touchable.tsx b/packages/react-native-gesture-handler/src/v3/components/Touchable/Touchable.tsx index 78b85aa37c..83b8a21100 100644 --- a/packages/react-native-gesture-handler/src/v3/components/Touchable/Touchable.tsx +++ b/packages/react-native-gesture-handler/src/v3/components/Touchable/Touchable.tsx @@ -67,7 +67,7 @@ export const Touchable = (props: TouchableProps) => { } }, [onLongPress, delayLongPress, wrappedLongPress]); - const onBegin = useCallback( + const onActivate = useCallback( (e: CallbackEventType) => { if (!e.pointerInside) { pointerState.current = PointerState.OUTSIDE; @@ -79,26 +79,10 @@ export const Touchable = (props: TouchableProps) => { pointerState.current = PointerState.INSIDE; }, - [startLongPressTimer, onPressIn] + [onPressIn, startLongPressTimer] ); - const onActivate = useCallback((e: CallbackEventType) => { - if (!e.pointerInside && longPressTimeout.current !== undefined) { - clearTimeout(longPressTimeout.current); - longPressTimeout.current = undefined; - } - }, []); - const onDeactivate = useCallback( - (e: EndCallbackEventType) => { - if (!e.canceled && !longPressDetected.current && e.pointerInside) { - onPress?.(e); - } - }, - [onPress] - ); - - const onFinalize = useCallback( (e: EndCallbackEventType) => { if (pointerState.current === PointerState.INSIDE) { onPressOut?.(e); @@ -110,8 +94,12 @@ export const Touchable = (props: TouchableProps) => { clearTimeout(longPressTimeout.current); longPressTimeout.current = undefined; } + + if (!e.canceled && !longPressDetected.current && e.pointerInside) { + onPress?.(e); + } }, - [onPressOut] + [onPress, onPressOut] ); const onUpdate = useCallback( @@ -155,10 +143,8 @@ export const Touchable = (props: TouchableProps) => { {...rippleProps} ref={ref ?? null} enabled={!disabled} - onBegin={onBegin} onActivate={onActivate} onDeactivate={onDeactivate} - onFinalize={onFinalize} onUpdate={onUpdate} defaultOpacity={defaultOpacity} defaultUnderlayOpacity={defaultUnderlayOpacity}