Repository navigation
Commit 653d519
authored
[Android] Keep the press alive while a
## Description
On Android, a `Pressable` with `requireToFail` dropped the whole press
on a quick tap: `onPressIn` fired, `onPressOut` and `onPress` never did.
When the awaited gesture won, the press was left without `onPressOut`
too. iOS was fine.
The long press gesture had an Android-only branch in `onTouchesUp` that
reset the state machine and finalized the press as soon as the finger
lifted. That assumes the button gesture has already finished by then.
With `requireToFail` the button is still awaiting the other gesture when
the finger lifts, so the reset ran first and the button's later FINALIZE
or CANCEL met an empty state machine.
The reset dates from #3571 and never acted on a working path: on a plain
tap, drag-out, scroll steal or parent pan the button finishes first and
`handlePressOut` has already finalized. On Android the native handler
always ends or cancels on pointer up, so the press is always closed
without it. This PR removes the branch, which also removes the
dependency on the orchestrator's delivery order.
The cancel branch added in #4633 loses its iOS gate, so a button
cancelled while the press is held delivers `onPressOut` on every native
platform.
## Test plan
Tested on a Pixel 9 Pro emulator and an iPhone 17 simulator with the
screen below. Tap each box once.
Before, on Android, the second box logs only `onPressIn`. After, both
boxes log `onPressIn`, `onPressOut`, `onPress`. A double tap on the
second box logs `onPressIn`, `onPressOut` and the double tap.
```tsx
import React, { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
Pressable,
useTapGesture,
} from 'react-native-gesture-handler';
export default function Repro() {
const [lines, setLines] = useState<string[]>([]);
const log = (line: string) =>
setLines((current) => [...current.slice(-7), line]);
const doubleTap = useTapGesture({
numberOfTaps: 2,
runOnJS: true,
onActivate: () => log('doubleTap activated'),
});
return (
<GestureHandlerRootView style={styles.container}>
<Pressable
onPressIn={() => log('plain: onPressIn')}
onPressOut={() => log('plain: onPressOut')}
onPress={() => log('plain: onPress')}>
<View style={styles.box}>
<Text>Plain Pressable</Text>
</View>
</Pressable>
<GestureDetector gesture={doubleTap}>
<View>
<Pressable
requireToFail={doubleTap}
onPressIn={() => log('requireToFail: onPressIn')}
onPressOut={() => log('requireToFail: onPressOut')}
onPress={() => log('requireToFail: onPress')}>
<View style={styles.box}>
<Text>Pressable with requireToFail</Text>
</View>
</Pressable>
</View>
</GestureDetector>
<Text style={styles.log}>{lines.join('\n')}</Text>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', paddingTop: 40, gap: 24 },
box: {
width: 300,
height: 80,
backgroundColor: '#b58df1',
alignItems: 'center',
justifyContent: 'center',
},
log: { width: 300, fontSize: 14 },
});
```
Also checked on both platforms that a plain tap, a hold, a drag out of
the box, a disable under the finger, a drag inside an RN and an RNGH
`ScrollView`, a parent pan, a nested `Pressable`, `hitSlop` and
`unstable_pressDelay` behave as before.Pressable waits for another gesture (#4639)1 parent 089f0af commit 653d519
1 file changed
Lines changed: 3 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
311 | 311 | | |
312 | 312 | | |
313 | 313 | | |
314 | | - | |
315 | | - | |
316 | | - | |
317 | | - | |
318 | | - | |
319 | | - | |
320 | | - | |
321 | 314 | | |
322 | 315 | | |
323 | 316 | | |
| |||
402 | 395 | | |
403 | 396 | | |
404 | 397 | | |
405 | | - | |
406 | | - | |
407 | | - | |
408 | | - | |
409 | | - | |
410 | | - | |
411 | | - | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
412 | 401 | | |
413 | 402 | | |
414 | 403 | | |
| |||
0 commit comments