@@ -16,6 +16,7 @@ import com.facebook.react.views.textinput.ReactEditText
1616import com.facebook.react.views.view.ReactViewGroup
1717import com.swmansion.gesturehandler.react.RNGestureHandlerRootHelper
1818import com.swmansion.gesturehandler.react.events.eventbuilders.NativeGestureHandlerEventDataBuilder
19+ import java.lang.reflect.Method
1920
2021class NativeViewGestureHandler : GestureHandler () {
2122 override val isContinuous = true
@@ -37,6 +38,17 @@ class NativeViewGestureHandler : GestureHandler() {
3738 var yieldsToContinuousGestures = false
3839 private set
3940
41+ /* *
42+ * When set, overrides whether the connected scrollable container delays the pressed state of
43+ * its children (see [android.view.ViewGroup.shouldDelayChildPressedState]). Only applies to
44+ * views implementing `HasChildPressedStateDelay` (React Native 0.87+), no-op otherwise. The
45+ * override is applied for the duration of a gesture.
46+ */
47+ var delaysChildPressedState: Boolean? = null
48+ private set
49+
50+ private var pressedStateDelayOverriddenView: View ? = null
51+
4052 private var hook: NativeViewGestureHandlerHook = defaultHook
4153
4254 private data class ActiveUpdateSnapshot (val pointerInside : Boolean , val numberOfPointers : Int , val pointerType : Int )
@@ -53,6 +65,7 @@ class NativeViewGestureHandler : GestureHandler() {
5365 disallowInterruption = DEFAULT_DISALLOW_INTERRUPTION
5466 yieldsToContinuousGestures = DEFAULT_YIELDS_TO_CONTINUOUS_GESTURES
5567 shouldCancelWhenOutside = DEFAULT_SHOULD_CANCEL_WHEN_OUTSIDE
68+ delaysChildPressedState = DEFAULT_DELAYS_CHILD_PRESSED_STATE
5669 }
5770
5871 override fun shouldRecognizeSimultaneously (handler : GestureHandler ): Boolean {
@@ -115,6 +128,14 @@ class NativeViewGestureHandler : GestureHandler() {
115128 is ReactTextView -> this .hook = TextViewHook ()
116129 is ReactViewGroup -> this .hook = ReactViewGroupHook ()
117130 }
131+
132+ delaysChildPressedState?.let { delays ->
133+ this .view?.let {
134+ if (trySetChildPressedStateDelay(it, delays)) {
135+ pressedStateDelayOverriddenView = it
136+ }
137+ }
138+ }
118139 }
119140
120141 override fun onHandle (event : MotionEvent , sourceEvent : MotionEvent ) {
@@ -179,6 +200,9 @@ class NativeViewGestureHandler : GestureHandler() {
179200 override fun onReset () {
180201 this .hook = defaultHook
181202 lastActiveUpdate = null
203+ // `null` restores the view's default pressed state delay behavior.
204+ pressedStateDelayOverriddenView?.let { trySetChildPressedStateDelay(it, null ) }
205+ pressedStateDelayOverriddenView = null
182206 }
183207
184208 override fun dispatchHandlerUpdate (event : MotionEvent ) {
@@ -214,6 +238,9 @@ class NativeViewGestureHandler : GestureHandler() {
214238 if (config.hasKey(KEY_YIELDS_TO_CONTINUOUS_GESTURES )) {
215239 handler.yieldsToContinuousGestures = config.getBoolean(KEY_YIELDS_TO_CONTINUOUS_GESTURES )
216240 }
241+ if (config.hasKey(KEY_DELAYS_CHILD_PRESSED_STATE )) {
242+ handler.delaysChildPressedState = config.getBoolean(KEY_DELAYS_CHILD_PRESSED_STATE )
243+ }
217244 }
218245
219246 override fun createEventBuilder (handler : NativeViewGestureHandler ) = NativeGestureHandlerEventDataBuilder (handler)
@@ -222,6 +249,7 @@ class NativeViewGestureHandler : GestureHandler() {
222249 private const val KEY_SHOULD_ACTIVATE_ON_START = " shouldActivateOnStart"
223250 private const val KEY_DISALLOW_INTERRUPTION = " disallowInterruption"
224251 private const val KEY_YIELDS_TO_CONTINUOUS_GESTURES = " yieldsToContinuousGestures"
252+ private const val KEY_DELAYS_CHILD_PRESSED_STATE = " delaysChildPressedState"
225253 }
226254 }
227255
@@ -230,6 +258,28 @@ class NativeViewGestureHandler : GestureHandler() {
230258 private const val DEFAULT_SHOULD_ACTIVATE_ON_START = false
231259 private const val DEFAULT_DISALLOW_INTERRUPTION = false
232260 private const val DEFAULT_YIELDS_TO_CONTINUOUS_GESTURES = false
261+ private val DEFAULT_DELAYS_CHILD_PRESSED_STATE : Boolean? = null
262+
263+ // `HasChildPressedStateDelay` was introduced in React Native 0.87 — it's accessed via
264+ // reflection so that the library compiles and runs on older versions.
265+ private val childPressedStateDelaySetter: Method ? by lazy {
266+ try {
267+ Class
268+ .forName(" com.facebook.react.uimanager.HasChildPressedStateDelay" )
269+ .getMethod(" setHasChildPressedStateDelay" , Boolean ::class .javaObjectType)
270+ } catch (e: ReflectiveOperationException ) {
271+ null
272+ }
273+ }
274+
275+ private fun trySetChildPressedStateDelay (view : View , value : Boolean? ): Boolean {
276+ val setter = childPressedStateDelaySetter ? : return false
277+ if (! setter.declaringClass.isInstance(view)) {
278+ return false
279+ }
280+ setter.invoke(view, value)
281+ return true
282+ }
233283
234284 private fun tryIntercept (view : View , event : MotionEvent ) = view is ViewGroup && view.onInterceptTouchEvent(event)
235285
0 commit comments