Repository navigation
[Android] v3 gestures below a native ScrollView still activate after the ScrollView takes over the touch #4581
Description
Description
On Android, a v3 gesture whose GestureDetector sits inside a React Native ScrollView/FlatList row is not cancelled when the scroll view takes over the touch stream. A tap that turned into a scroll, or a tap that only stopped a fling, still activates when the finger lifts.
Expected: the tap is cancelled once the scroll view takes over. That's what happens with v2 Gesture.Tap() (the root cancels legacy handlers on the disallow request), with v3 buttons since #4441, and on iOS.
We hit this in production after migrating a swipeable list row from Gesture.Race(pan, tap) to useCompetingGestures(usePanGesture(...), useTapGesture(...)). Scrolling the list started opening the row the scroll began on, and tapping to stop a fling opened the row under the finger.
Cause, as far as I can tell from the sources:
- The scroll view's
requestDisallowInterceptTouchEvent(true)only bubbles up. RNGestureHandlerRootHelpercancels legacy action types only ([Android] Properly handlerequestDisallowInterceptTouchEventfor v3 #4367), and theRNGestureHandlerDetectorViewoverride only reaches detectors above the requester.- The deferred sweep from [Android] Fix buttons firing press events when a scroll takes over the touch #4441 (
cancelHandlersOnNativeTouchGrab) only coversNativeViewGestureHandlers whose hook opts in, that is, buttons. - A detector inside a row sits below the requester, so nothing cancels its handlers. The row moves with the finger, so
shouldCancelWhenOutsidedoesn't trip, and a tap has no defaultmaxDistance.
Steps to reproduce
- Render the gist screen on Android. The List tab is a
FlatListof rows, each with a v3 tap competing with a horizontal pan, and the header counts what fires. - Put a finger on a row, drag to scroll, and lift within the tap's 500 ms.
tapincrements: 4 of 5 trials on an emulator.
As a side effect, when the phantom tap activates on lift the root starts intercepting, so the list receives a cancel instead of the release and stops dead instead of flinging. Momentum after a drag showed up in 1 of 5 trials, against 5 of 5 once the tap is cancelled. - Fling the list, then tap a row to stop it.
tapincrements: 5 of 5. - For comparison, tapping a settled row and swiping a row sideways behave as expected.
Trials were driven with adb shell input swipe/input tap on main (a530859) in apps/expo-example.
A link to a Gist, an Expo Snack or a link to a repository based on this template that reproduces the bug.
https://gist.github.com/JacquesLeupin/0a30f72fcc02c82144f4b374e55d19a3
Gesture Handler version
3.3.0, and main (a530859)
React Native version
0.87.1 in our app; 0.86.2 in apps/expo-example
Platforms
Android
JavaScript runtime
Hermes
Workflow
React Native (without Expo) in our app; Expo dev client for the repro
Architecture
New Architecture (Fabric)
Build type
Release mode in our app; debug for the repro
Device
Real device and Android emulator
Device model
Pixel 9 (Android 17); Android 16 emulator (Pixel 9 Pro XL image)
Acknowledgements
Yes
Possible fix
Apply the #4441 rule to detector handlers in the same deferred sweep: cancel a non-active detector handler when the grab happens after DOWN, or when it happens on DOWN and the DOWN never reached its detector (a fling catch). Recording the last native DOWN in RNGestureHandlerDetectorView.dispatchTouchEvent is enough to tell those cases apart, and an eager disallow on DOWN that still lets the event through (the nested pager case from #2383) then cancels nothing. I have a PR ready and will link it here.
Activity
m-bert commentedon Oct 8, 2026
Hi @JacquesLeupin! Thank you for reporting this issue.
Correct me if I'm wrong, but this issue was already discussed in #4547. Can you try if using
FlatListfrom Gesture Handler solves this problem?JacquesLeupin commentedon Oct 8, 2026
Thanks @m-bert, appreciate you taking a look.
I think this one's a little different from #4547. That was a
Touchableinside Gesture Handler's own scroll views. This isGestureDetectorgestures (taps, pans,ReanimatedSwipeable) inside React Native'sScrollView/FlatList, and it happens on a normal drag too, not just when stopping a fling.I did try Gesture Handler's
FlatList. Onmainit works, but without #4548 (so on 3.3.0 today) a tap that stops a fling still fires. Swapping out every scroll container isn't really practical for us, and plenty of list libraries render React Native'sScrollViewunder the hood anyway. Since #4441 already madeTouchablebehave inside React Native's scroll views, #4582 brings detector gestures in line with that.Happy to rework it if you'd like a different approach.
m-bert commentedon Oct 9, 2026
Hi @JacquesLeupin!
I agree with you 😅 I did respond in the copilot review thread, please take a look.
[Android] Cancel detector gestures when a native view above them take…
fix: bump to latest rngh (#3878)