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 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 8c808e8a71..0c53dab2f4 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 = (