From d57b46835273982b96ad4dd7daea02473c5d341d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 8 Dec 2025 13:00:43 +0100 Subject: [PATCH 1/2] Pass correct prop --- .../src/v3/components/GestureComponents.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/src/v3/components/GestureComponents.tsx b/packages/react-native-gesture-handler/src/v3/components/GestureComponents.tsx index 8b6d32552c..bf4bccd187 100644 --- a/packages/react-native-gesture-handler/src/v3/components/GestureComponents.tsx +++ b/packages/react-native-gesture-handler/src/v3/components/GestureComponents.tsx @@ -77,7 +77,7 @@ export const ScrollView = ( Date: Tue, 9 Dec 2025 14:37:34 +0100 Subject: [PATCH 2/2] Fix scroll android --- .../core/NativeViewGestureHandler.kt | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) 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 42b65158b1..4800c5aa1a 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 @@ -201,6 +201,12 @@ class NativeViewGestureHandler : GestureHandler() { private fun tryIntercept(view: View, event: MotionEvent) = view is ViewGroup && view.onInterceptTouchEvent(event) private val defaultHook = object : NativeViewGestureHandlerHook {} + + enum class ScrollDirection(val value: Int) { + UP(-1), + DOWN(1), + NONE(0), + } } interface NativeViewGestureHandlerHook { @@ -304,6 +310,7 @@ class NativeViewGestureHandler : GestureHandler() { private val handler: NativeViewGestureHandler, private val swipeRefreshLayout: ReactSwipeRefreshLayout, ) : NativeViewGestureHandlerHook { + private var lastY: Float? = null override fun wantsToHandleEventBeforeActivation() = true override fun handleEventBeforeActivation(event: MotionEvent) { @@ -323,11 +330,32 @@ class NativeViewGestureHandler : GestureHandler() { it is NativeViewGestureHandler } - // If handler was found, it's active and the ScrollView is not at the top, fail the RefreshControl - if (scrollHandler != null && scrollHandler.state == STATE_ACTIVE && scroll.scrollY > 0) { + // In old API ScrollView was detecting scroll even if RefreshControl hasn't been cancelled yet. + // This doesn't work on new API, therefore we check scroll direction. This shouldn't affect old APIs. + // To determine scroll direction, we will compare current event with previous one. + // Note: Scrolling up is handled by `canScrollVertically` method. + val scrollDirection = lastY?.let { + val dy = it - event.y + + when { + dy < 0 -> ScrollDirection.UP + dy > 0 -> ScrollDirection.DOWN + else -> ScrollDirection.NONE + } + } ?: ScrollDirection.NONE + + // We want to fail RefreshControl if we find active ScrollView handler and we either: + // 1. scroll down, + // 2. scroll up when we are not at the top of the list. + if (scrollHandler != null && + scrollHandler.state == STATE_ACTIVE && + (scrollDirection == ScrollDirection.DOWN || scroll.canScrollVertically(ScrollDirection.UP.value)) + ) { handler.fail() } + lastY = event.y + // The drawback is that the smooth transition from scrolling to refreshing in a single swipe // is impossible this way and two swipes are required: // - one to go back to top