Closed m-bert closed 3 months ago
LongPress permanently stops working after being cancelled by placing additional finger down while activated.
That's strange, I've just tested it with parameters that you've provided and eI can't see this problem. What I do see though, is that we get onEnd
and onFinalize
with false
triggered, I'll check what's going on.
Are you sure that it permanently stops working?
Are you sure that it permanently stops working?
Tested it again, it stops working for the very next to-be activation, but works again after that.
Video
https://github.com/user-attachments/assets/62c9becf-83cd-4d58-bce4-3238fe539842
@m-bert
Another possibly related issue - given the following code:
When gesture is cancelled by dragging the finger out-of-bounds, the next 2 attempts to activate it fail, before the 3rd one is successful.
Issue only occurs on Android
Reproduction video:
https://github.com/user-attachments/assets/cf50a9b7-4326-4897-a64f-c287004a55b3
Tested it again, it stops working for the very next to-be activation, but works again after that.
Everything works fine in my case. @j-piasecki, could you check it if you can reproduce it?
When gesture is cancelled by dragging the finger out-of-bounds, the next 2 attempts to activate it fail, before the 3rd one is successful.
In my case only the first attempt fails, second one activates as expected. I'll check that.
In my case only the first attempt fails, second one activates as expected. I'll check that.
Okay, as I thought, shouldCancelWhenOutside
follows different logic. If handler is to be cancelled by this flag, onHandle
method is not called (well, it is, but in GestureHandler
, not in the specific one). Because of that, currentPointers
were not reseted.
To avoid much changes in general logic, I've set currentPointers
to 1
when we first start gesture (i.e. it is in UNDETERMINED
state).
Let me know if that satisfies you, @j-piasecki @latekvo.
Changes were made in this commit.
I confirm all previously failing test cases now behave correctly on my end π
To avoid much changes in general logic, I've set currentPointers to 1 when we first start gesture (i.e. it is in UNDETERMINED state).
Wouldn't onReset
be a better pick here?
Description
LongPressGestureHandler
doesn't support multiple pointers. Since default value ofnumberOfTouchesRequired
on iOS is1
, handler gets cancelled if you put more than 1 pointer on it.This PR adds
numberOfPointers
modifier forLongPress
so that now users can specify how many pointers are needed forLongPress
to activate.Web
On
web
you could see similar problem, which was probably caused by callingcheckDistanceFail
insideonPointerMove
- if you added another pointer, start position was still at the first pointer position, not in the midpoint. Therefore it was easy to accidentally cancel handler.macOS
I'm not sure if this feature is required on
macOS
. In many places we just assume that handlers use only1
pointer (or2
in case ofpinch
/rotation
). I've added required property to recognizer, but I have not tested that πTest plan
Tested on slightly modified LongPress example from macOS:
Test code
```tsx import { StyleSheet, View } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { interpolateColor, useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated'; const Durations = { LongPress: 750, Reset: 350, Scale: 120, }; const Colors = { Initial: '#0a2688', Loading: '#6fcef5', Success: '#32a852', Fail: '#b02525', }; export default function LongPressExample() { const isPressed = useSharedValue(false); const colorProgress = useSharedValue(0); const color1 = useSharedValue(Colors.Initial); const color2 = useSharedValue(Colors.Loading); const animatedStyles = useAnimatedStyle(() => { const backgroundColor = interpolateColor( colorProgress.value, [0, 1], [color1.value, color2.value] ); return { transform: [ { scale: withTiming(isPressed.value ? 1.2 : 1, { duration: Durations.Scale, }), }, ], backgroundColor, }; }); const g = Gesture.LongPress() .onBegin(() => { console.log('onBegin'); isPressed.value = true; colorProgress.value = withTiming(1, { duration: Durations.LongPress, }); }) .onStart(() => console.log('onStart')) .onEnd(() => console.log('onEnd')) .onFinalize((_, success) => { console.log('onFinalize', success); isPressed.value = false; color1.value = Colors.Initial; color2.value = success ? Colors.Success : Colors.Fail; colorProgress.value = withTiming( 0, { duration: Durations.Reset, }, () => { color2.value = Colors.Loading; } ); }) .onTouchesDown(() => console.log('onTouchesDown')) .onTouchesMove(() => console.log('onTouchesMove')) .onTouchesUp(() => console.log('onTouchesUp')) .minDuration(Durations.LongPress) .numberOfPointers(2) .maxDistance(100); return (