Repository navigation
Commit 4727219
authored
[Android] Dispatch
## Description
On Android `onPressIn` was sent when the `Native` gesture began, i.e. on
the `DOWN`. The platform defers the pressed state inside scrolling
containers and drops it when a parent intercepts the touch, so a tap
that stops a fling fired `onPressIn` and `onPressOut` with no `onPress`,
and feedback driven from `onPressIn` ran ahead of the native ripple
(#4515).
`onPressIn` and the long press timer now follow `setPressed(true)`, the
moment the native press feedback starts. The long press timer is still
measured from the `DOWN`. `onPressOut`, `onPress` and the
`pressOutAfterAnimation` hold are unchanged.
Fixes #4515
## Test plan
<details>
<summary>Tested on the following code:</summary>
```tsx
import React, { useRef, useState } from 'react';
import {
Animated,
FlatList as RNFlatList,
StyleSheet,
Text,
View,
} from 'react-native';
import { FlatList, Touchable } from 'react-native-gesture-handler';
// onPressIn follows the native press (#4515, #4547). Every row scales down from onPressIn and
// back from onPressOut, next to the native ripple, so the two should start together.
// - Fling the list, then stop it with a finger on a row and lift: no callback should fire.
// - Start a fast swipe on a row: no onPressIn. Press, hold, then drag: onPressIn with the
// ripple, onPressOut when the scroll takes over.
// - Hold a row: onLongPress at ~500 ms, in both lists.
const ROWS = Array.from({ length: 200 }, (_, i) => i);
const LONG_PRESS_MS = 500;
const COLORS = ['#6750a4', '#0b6e4f', '#b3261e', '#1d4ed8', '#b45309'];
type Counts = {
pressIn: number;
pressOut: number;
press: number;
longPress: number;
};
const EMPTY_COUNTS: Counts = {
pressIn: 0,
pressOut: 0,
press: 0,
longPress: 0,
};
function Row({
index,
count,
}: {
index: number;
count: (key: keyof Counts) => void;
}) {
const scale = useRef(new Animated.Value(1)).current;
const animateTo = (value: number) =>
Animated.timing(scale, {
toValue: value,
duration: 100,
useNativeDriver: true,
}).start();
const color = COLORS[index % COLORS.length];
return (
<Touchable
androidRipple={{ color: 'rgba(0, 0, 0, 0.12)' }}
delayLongPress={LONG_PRESS_MS}
onPressIn={() => {
count('pressIn');
animateTo(0.95);
}}
onPressOut={() => {
count('pressOut');
animateTo(1);
}}
onPress={() => count('press')}
onLongPress={() => count('longPress')}>
<Animated.View style={[styles.row, { transform: [{ scale }] }]}>
<View style={[styles.avatar, { backgroundColor: color }]}>
<Text style={styles.avatarText}>{index}</Text>
</View>
<View>
<Text style={styles.rowTitle}>Row {index}</Text>
<Text style={styles.rowSubtitle}>
scales on onPressIn, ripple on native press
</Text>
</View>
</Animated.View>
</Touchable>
);
}
function Stat({ label, value }: { label: string; value: number }) {
return (
<View style={styles.stat}>
<Text style={styles.statValue}>{value}</Text>
<Text style={styles.statLabel}>{label}</Text>
</View>
);
}
export default function PressInNativeTimingExample() {
const [useRNGHList, setUseRNGHList] = useState(true);
const [counts, setCounts] = useState<Counts>(EMPTY_COUNTS);
const count = (key: keyof Counts) =>
setCounts((c) => ({ ...c, [key]: c[key] + 1 }));
const List = useRNGHList ? FlatList : RNFlatList;
return (
<View style={styles.root}>
<View style={styles.panel}>
<View style={styles.stats}>
<Stat label="pressIn" value={counts.pressIn} />
<Stat label="pressOut" value={counts.pressOut} />
<Stat label="press" value={counts.press} />
<Stat label="longPress" value={counts.longPress} />
</View>
<View style={styles.actions}>
<Touchable
style={styles.button}
onPress={() => setUseRNGHList((v) => !v)}>
<Text style={styles.buttonText}>
{useRNGHList ? 'RNGH FlatList' : 'RN FlatList'}
</Text>
</Touchable>
<Touchable
style={[styles.button, styles.buttonSecondary]}
onPress={() => setCounts(EMPTY_COUNTS)}>
<Text style={[styles.buttonText, styles.buttonSecondaryText]}>
Reset
</Text>
</Touchable>
</View>
<Text style={styles.hint}>
Fling, then stop the list with a finger on a row: nothing should
count. Hold a row for onLongPress.
</Text>
</View>
<List
key={useRNGHList ? 'rngh' : 'rn'}
data={ROWS}
keyExtractor={(i) => String(i)}
contentContainerStyle={styles.content}
renderItem={({ item }) => <Row index={item} count={count} />}
/>
</View>
);
}
const styles = StyleSheet.create({
root: { flex: 1, backgroundColor: '#f4f3f8' },
panel: {
margin: 16,
marginBottom: 8,
padding: 16,
borderRadius: 16,
backgroundColor: '#fff',
gap: 12,
shadowColor: '#000',
shadowOpacity: 0.08,
shadowRadius: 12,
shadowOffset: { width: 0, height: 4 },
elevation: 3,
},
stats: { flexDirection: 'row', justifyContent: 'space-between' },
stat: { alignItems: 'flex-start' },
statValue: { fontSize: 24, fontWeight: '700', color: '#1c1b1f' },
statLabel: { fontSize: 12, color: '#79747e' },
actions: { flexDirection: 'row', gap: 8 },
button: {
flex: 1,
alignItems: 'center',
paddingVertical: 10,
borderRadius: 10,
backgroundColor: '#6750a4',
},
buttonText: { color: '#fff', fontWeight: '600' },
buttonSecondary: {
flex: 0,
paddingHorizontal: 16,
backgroundColor: '#e8e8ee',
},
buttonSecondaryText: { color: '#1c1b1f' },
hint: { color: '#5f5c66', fontSize: 13, lineHeight: 18 },
content: { paddingHorizontal: 16, paddingBottom: 16, gap: 8 },
row: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
padding: 12,
borderRadius: 14,
backgroundColor: '#fff',
},
avatar: {
width: 40,
height: 40,
borderRadius: 20,
alignItems: 'center',
justifyContent: 'center',
},
avatarText: { color: '#fff', fontWeight: '700' },
rowTitle: { fontSize: 16, fontWeight: '600', color: '#1c1b1f' },
rowSubtitle: { fontSize: 12, color: '#79747e', marginTop: 2 },
});
```
</details>onPressIn when the native press starts (#4575)1 parent 13f4c7b commit 4727219
1 file changed
Lines changed: 49 additions & 9 deletions
File tree
- packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
738 | 738 | | |
739 | 739 | | |
740 | 740 | | |
| 741 | + | |
| 742 | + | |
| 743 | + | |
| 744 | + | |
741 | 745 | | |
742 | 746 | | |
743 | 747 | | |
| |||
773 | 777 | | |
774 | 778 | | |
775 | 779 | | |
776 | | - | |
| 780 | + | |
| 781 | + | |
777 | 782 | | |
778 | 783 | | |
779 | | - | |
780 | | - | |
781 | | - | |
782 | | - | |
783 | | - | |
784 | | - | |
785 | | - | |
786 | | - | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
787 | 790 | | |
788 | 791 | | |
789 | 792 | | |
| |||
819 | 822 | | |
820 | 823 | | |
821 | 824 | | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
822 | 861 | | |
823 | 862 | | |
824 | 863 | | |
| |||
1595 | 1634 | | |
1596 | 1635 | | |
1597 | 1636 | | |
| 1637 | + | |
1598 | 1638 | | |
1599 | 1639 | | |
1600 | 1640 | | |
| |||
0 commit comments