Repository navigation
Commit 6c2a8df
authored
[Android] Stop nested scroll when the root starts intercepting (#4592)
## Description
When a gesture activates during a pull-to-refresh on an RN `FlatList`
with an RN `RefreshControl`, the spinner parks half-pulled and
`onRefresh` fires only on the next touch. Reported with
`ReanimatedSwipeable` rows in #4563.
RN drives the pull through nested scrolling and `SwipeRefreshLayout`
finishes it only in `onStopNestedScroll`, which Android triggers from
`View.dispatchTouchEvent` by calling `stopNestedScroll()` after the
final UP or CANCEL. When the root view starts intercepting,
`cancelTouchesInInterceptedViews` feeds `ACTION_CANCEL` straight to
`onTouchEvent`, so that cleanup never runs. Same omission #4492 fixed on
the `NativeViewGestureHandler` path.
The sweep now calls `stopNestedScroll()` after `onTouchEvent`, as
[`View.dispatchTouchEvent`
does](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/view/View.java;l=16587).
It is a no-op without an open nested scroll. An interrupted pull now
snaps back, or fires `onRefresh` if it was past the threshold.
## Test plan
- Before #4582 (main at 993bbe7): RN `FlatList` + RN `RefreshControl`
with `ReanimatedSwipeable` rows and with bare v3 pan rows, drag a row
down then sideways. Short pull snaps back and the row still swipes, long
pull fires `onRefresh` immediately. Previously the spinner parked until
the next touch.
- On current main: single-finger drag leaves the pull to the scroll view
(gesture cancelled by #4582). Hold a pull with one finger and swipe
another row with a second finger: spinner snaps back or refreshes at
once instead of parking.
- GH `FlatList` + RN `RefreshControl`, GH `FlatList` + GH
`RefreshControl`, plain pulls and plain row swipes unchanged. iOS
unaffected.
<details>
<summary>Repro</summary>
```tsx
import { useCallback, useState } from 'react';
import { FlatList, RefreshControl, StyleSheet, Text, View } from 'react-native';
import { GestureDetector, GestureHandlerRootView, usePanGesture } from 'react-native-gesture-handler';
const DATA = Array.from({ length: 30 }, (_, i) => `Row ${i}`);
function Row({ item }: { item: string }) {
const pan = usePanGesture({ activeOffsetX: [-10, 10] });
return (
<GestureDetector gesture={pan}>
<View style={styles.row}>
<Text>{item}</Text>
</View>
</GestureDetector>
);
}
export default function App() {
const [refreshing, setRefreshing] = useState(false);
const onRefresh = useCallback(() => {
setRefreshing(true);
setTimeout(() => setRefreshing(false), 1000);
}, []);
return (
<GestureHandlerRootView style={styles.container}>
<FlatList
data={DATA}
keyExtractor={(item) => item}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
renderItem={({ item }) => <Row item={item} />}
/>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
row: { padding: 20, backgroundColor: 'white', borderBottomWidth: 1, borderColor: '#eee' },
});
```
<details>1 parent cdb3c9d commit 6c2a8df
1 file changed
Lines changed: 1 addition & 0 deletions
File tree
- packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
774 | 774 | | |
775 | 775 | | |
776 | 776 | | |
| 777 | + | |
777 | 778 | | |
778 | 779 | | |
779 | 780 | | |
| |||
0 commit comments