diff --git a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt index 7f878bf8f6..7451e47a51 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt @@ -167,6 +167,10 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager(), R * [com.swmansion.gesturehandler.NativeViewGestureHandler.onHandle] */ @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { + if (event.action == MotionEvent.ACTION_CANCEL) { + tryFreeingResponder() + } + val eventTime = event.eventTime val action = event.action // always true when lastEventTime or lastAction have default value (-1) @@ -267,7 +271,7 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager(), R } override fun drawableHotspotChanged(x: Float, y: Float) { - if (responder == null || responder === this) { + if (touchResponder == null || touchResponder === this) { super.drawableHotspotChanged(x, y) } } @@ -280,19 +284,31 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager(), R return isResponder } + override fun afterGestureEnd(event: MotionEvent) { + tryFreeingResponder() + isTouched = false + } + + private fun tryFreeingResponder() { + if (touchResponder === this) { + touchResponder = null + soundResponder = this + } + } + private fun tryGrabbingResponder(): Boolean { if (isChildTouched()) { return false } - if (responder == null) { - responder = this + if (touchResponder == null) { + touchResponder = this return true } return if (exclusive) { - responder === this + touchResponder === this } else { - !(responder?.exclusive ?: false) + !(touchResponder?.exclusive ?: false) } } @@ -313,7 +329,8 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager(), R override fun performClick(): Boolean { // don't preform click when a child button is pressed (mainly to prevent sound effect of // a parent button from playing) - return if (!isChildTouched()) { + return if (!isChildTouched() && soundResponder == this) { + soundResponder = null super.performClick() } else { false @@ -327,22 +344,23 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager(), R // when canStart is called eventually, tryGrabbingResponder will return true if the button // already is a responder if (pressed) { - tryGrabbingResponder() + if (tryGrabbingResponder()) { + soundResponder = this + } } // button can be pressed alongside other button if both are non-exclusive and it doesn't have // any pressed children (to prevent pressing the parent when children is pressed). - val canBePressedAlongsideOther = !exclusive && responder?.exclusive != true && !isChildTouched() + val canBePressedAlongsideOther = !exclusive && touchResponder?.exclusive != true && !isChildTouched() - if (!pressed || responder === this || canBePressedAlongsideOther) { + if (!pressed || touchResponder === this || canBePressedAlongsideOther) { // we set pressed state only for current responder or any non-exclusive button when responder // is null or non-exclusive, assuming it doesn't have pressed children isTouched = pressed super.setPressed(pressed) } - if (!pressed && responder === this) { + if (!pressed && touchResponder === this) { // if the responder is no longer pressed we release button responder - responder = null isTouched = false } } @@ -354,7 +372,8 @@ class RNGestureHandlerButtonViewManager : ViewGroupManager(), R companion object { var resolveOutValue = TypedValue() - var responder: ButtonViewGroup? = null + var touchResponder: ButtonViewGroup? = null + var soundResponder: ButtonViewGroup? = null var dummyClickListener = OnClickListener { } } } diff --git a/example/src/App.tsx b/example/src/App.tsx index 92a92bb9d6..186b583313 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -17,6 +17,7 @@ import { TouchablesIndex, TouchableExample } from './release_tests/touchables'; import Rows from './release_tests/rows'; import Fling from './release_tests/fling'; import NestedTouchables from './release_tests/nestedTouchables'; +import NestedButtons from './release_tests/nestedButtons'; import NestedGestureHandlerRootViewWithModal from './release_tests/nestedGHRootViewWithModal'; import { PinchableBox } from './recipes/scaleAndRotate'; import PanAndScroll from './recipes/panAndScroll'; @@ -99,6 +100,10 @@ const EXAMPLES: ExamplesSection[] = [ name: 'Nested Touchables - issue #784', component: NestedTouchables as React.ComponentType, }, + { + name: 'Nested buttons (sound & ripple on Android)', + component: NestedButtons, + }, { name: 'Double pinch & rotate', component: DoublePinchRotate }, { name: 'Double draggable', component: DoubleDraggable }, { name: 'Rows', component: Rows }, diff --git a/example/src/release_tests/nestedButtons/index.tsx b/example/src/release_tests/nestedButtons/index.tsx new file mode 100644 index 0000000000..b7ea64bbef --- /dev/null +++ b/example/src/release_tests/nestedButtons/index.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { View } from 'react-native'; + +import { RectButton } from 'react-native-gesture-handler'; + +export default function Example() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/example/yarn.lock b/example/yarn.lock index 3daf165a45..7c78b7e7ab 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -3699,9 +3699,9 @@ integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== "@types/react-native@^0.69.6": - version "0.69.6" - resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.69.6.tgz#b792b7eb024a14869fdbbe97536e6014cb3be731" - integrity sha512-jx1QdJT3CdQc42EpoIGu22F1wrPZjmC/CNkfR5sRs5GxloJzthuICK7CKqAGEo2SekPs+YYzhbzrJGi1IrG5Lg== + version "0.69.13" + resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.69.13.tgz#f947b2bd00439126f8101441945739698bb5051e" + integrity sha512-jEKIkqKjmYI7NeRssLrn/zRtFeI6S1FNe5p45OKbBxrQArXpF2lJelztFTEZKIv1PSfYKcAk5x6+1sdZFEFAkA== dependencies: "@types/react" "*"