Skip to content

Commit 84550ec

Browse files
authored
[Android] Fix ScrollView (#3867)
## Description I've noticed that `ScrollView` did not work on Android. There were two issues with that: 1. `GHScrollView` received wrong prop. For some reason linter did not catch that. 2. `RefreshControl` was blocking scroll at the top of the list. This PR changes prop name to the correct one and adds check for scrolling direction so that `RefreshControl` no longer blocks scrolling. ## Test plan **ScrollView** and **FlatList** examples.
1 parent 6afb378 commit 84550ec

File tree

2 files changed

+31
-3
lines changed

2 files changed

+31
-3
lines changed

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ class NativeViewGestureHandler : GestureHandler() {
201201
private fun tryIntercept(view: View, event: MotionEvent) = view is ViewGroup && view.onInterceptTouchEvent(event)
202202

203203
private val defaultHook = object : NativeViewGestureHandlerHook {}
204+
205+
enum class ScrollDirection(val value: Int) {
206+
UP(-1),
207+
DOWN(1),
208+
NONE(0),
209+
}
204210
}
205211

206212
interface NativeViewGestureHandlerHook {
@@ -304,6 +310,7 @@ class NativeViewGestureHandler : GestureHandler() {
304310
private val handler: NativeViewGestureHandler,
305311
private val swipeRefreshLayout: ReactSwipeRefreshLayout,
306312
) : NativeViewGestureHandlerHook {
313+
private var lastY: Float? = null
307314
override fun wantsToHandleEventBeforeActivation() = true
308315

309316
override fun handleEventBeforeActivation(event: MotionEvent) {
@@ -323,11 +330,32 @@ class NativeViewGestureHandler : GestureHandler() {
323330
it is NativeViewGestureHandler
324331
}
325332

326-
// If handler was found, it's active and the ScrollView is not at the top, fail the RefreshControl
327-
if (scrollHandler != null && scrollHandler.state == STATE_ACTIVE && scroll.scrollY > 0) {
333+
// In old API ScrollView was detecting scroll even if RefreshControl hasn't been cancelled yet.
334+
// This doesn't work on new API, therefore we check scroll direction. This shouldn't affect old APIs.
335+
// To determine scroll direction, we will compare current event with previous one.
336+
// Note: Scrolling up is handled by `canScrollVertically` method.
337+
val scrollDirection = lastY?.let {
338+
val dy = it - event.y
339+
340+
when {
341+
dy < 0 -> ScrollDirection.UP
342+
dy > 0 -> ScrollDirection.DOWN
343+
else -> ScrollDirection.NONE
344+
}
345+
} ?: ScrollDirection.NONE
346+
347+
// We want to fail RefreshControl if we find active ScrollView handler and we either:
348+
// 1. scroll down,
349+
// 2. scroll up when we are not at the top of the list.
350+
if (scrollHandler != null &&
351+
scrollHandler.state == STATE_ACTIVE &&
352+
(scrollDirection == ScrollDirection.DOWN || scroll.canScrollVertically(ScrollDirection.UP.value))
353+
) {
328354
handler.fail()
329355
}
330356

357+
lastY = event.y
358+
331359
// The drawback is that the smooth transition from scrolling to refreshing in a single swipe
332360
// is impossible this way and two swipes are required:
333361
// - one to go back to top

packages/react-native-gesture-handler/src/v3/components/GestureComponents.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export const ScrollView = (
7777
<GHScrollView
7878
{...rest}
7979
ref={props.ref}
80-
updateGesture_CAN_CAUSE_INFINITE_RERENDER={updateGesture}
80+
onGestureUpdate_CAN_CAUSE_INFINITE_RERENDER={updateGesture}
8181
// @ts-ignore we don't pass `refreshing` prop as we only want to override the ref
8282
refreshControl={
8383
refreshControl

0 commit comments

Comments
 (0)