question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Nested Pan / LongPressGestureHandlers with different behavior Android / iOS

See original GitHub issue

Description

I’m trying to get a list (ScrollView) that contains items that can be reordered. I struggled a bit with the implementation, but got it working on iOS like with nested LongPress / PanGesture-Handlers:

I got both the Pan event and the ScrollView working on iOS by setting activeOffsetX and activeOffsetY as states, which are updated upon LongPress events. The offset defaults to [-100, 100], which causes flicks to scroll the all items on the ScrollView. After a long press, I set the offset to [0,0], and I can start dragging around the items on the UI. But as I said, that only works on iOS, so there’s another distinction between the platforms.

On Android, this doesn’t work at all. I noticed a few things:

  • The LongPressEvent becomes active immediately, there is not delay at all (I can quickly flick on the UI, and the OnActive event fires). Setting minDurationMs has no effect whatsoever either.
  • The LongPress gesture is cancelled after a little movement, whereas the iOS gesture doesn’t. I fixed this by explicitly setting shouldCancelWhenOutside and minDist.

Here’s the relevant code to my items. As said, at runtime, they are rendered as children of a ScrollView parent component:

const [isLongPress, setLongPress] = useState(false);
const [offset, setOffset] = useState([-100, 100]);

const onLongPressGestureEvent = useAnimatedGestureHandler<LongPressGestureHandlerGestureEvent>({
    onActive: (nativeEvent) => {
        if(!isLongPress) {
            runOnJS(setLongPress)(true);
            runOnJS(setOffset)([-5, 5]);
        }
    },
    onFinish: () => {
        runOnJS(setLongPress)(false);
        runOnJS(setOffset)([-100, 100]);
    }
});


<Animated.View style={style}>
    <PanGestureHandler
        activeOffsetX={offset}
        activeOffsetY={offset}
        ref={panRef}
        simultaneousHandlers={pressRef}
        enabled={true}
        onGestureEvent={onPanGestureEvent}>
        <Animated.View style={StyleSheet.absoluteFill}>
            <LongPressGestureHandler
                shouldCancelWhenOutside={false}
                maxDist={1000}
                ref={pressRef}
                simultaneousHandlers={panRef}
                onGestureEvent={onLongPressGestureEvent}>
                <Animated.View style={StyleSheet.absoluteFill}>
                    {children}
                </Animated.View>
            </LongPressGestureHandler>
        </Animated.View>
    </PanGestureHandler>
</Animated.View>

What’s happening

On Android, short flicks do properly scroll the ScrollView. If I long-press and then start dragging, no scrolling happens (that’s good), but also no panning. While dragging my finger, I get continuous LongPress OnActive events, but I get only on Pan OnStart event but no other pan events (active or end).

On iOS, I get simultanous LongPress active / Pan active events, so I can animated the dragged item.

Package versions

  • React Native: 0.63.4
  • React Native Gesture Handler: 1.9.0
  • Reanimated 2 RC 0

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:3
  • Comments:8 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
pkvovcommented, Jul 12, 2021

some problem, IOS works fine, no luck on Android,

const translation = useVector();
const panRef = useRef(null);
const longPressRef = useRef(null);
const isDragging = useSharedValue(false);

const translateX = useDerivedValue(() => {
  if (isDragging.value) {
    return translation.x.value;
  }
  return withSpring(translation.x.value);
});

const translateY = useDerivedValue(() => {
  if (isDragging.value) {
    return translation.y.value;
  }
  return withSpring(translation.y.value);
});

const style = useAnimatedStyle(() => {
  return {
    position: "absolute",
    top: 0,
    left: 0,
    transform: [
      { translateX: translateX.value },
      { translateY: translateY.value }
    ],
  };
});


const handleLongPressStateChange = (evt) => {
  console.log("handleLongPressStateChange");
  if (evt.nativeEvent.state == State.ACTIVE) {
    console.log("set isDragging to True");
    isDragging.value = true;
  }
  if (evt.nativeEvent.state == State.CANCELLED ||
    evt.nativeEvent.state == State.END) {
    console.log("set isDragging to False");
    isDragging.value = false;
  }
};
const onPanGestureEvent = (evt) => {
  console.log("onPanGestureEvent");
  console.log("isDragging: ", isDragging.value);
  if (isDragging.value) {
    const {
      nativeEvent: { translationX: x, translationY: y },
    } = evt;

    console.log("onPanGestureEvent onActive change x and y");
    translation.x.value = x;
    translation.y.value = y;
  }
};

function nestingLongPressWithPan() {
  return (
    <Animated.View width={width} height={height - 100}>
      <PanGestureHandler
        ref={panRef}
        simultaneousHandlers={longPressRef}
        onGestureEvent={onPanGestureEvent}
        onHandlerStateChange={(evt) => {
          console.log("onHandlerStateChange");
          if (isDragging.value && evt.nativeEvent.state == State.END) {
            console.log("set isDragging to False");
            isDragging.value = false;

            translation.x.value = 0;
            translation.y.value = 0;
          }
        }}
      >
        <Animated.View>
          <LongPressGestureHandler
            ref={longPressRef}
            minDurationMs={500}
            maxDist={400}
            onHandlerStateChange={handleLongPressStateChange}
          >
            <Animated.View
              x="0"
              y="0"
              style={[style]}
            >
              <Text>
                this text can drag & drop!!!
            </Text>
            </Animated.View>
          </LongPressGestureHandler>
        </Animated.View>
      </PanGestureHandler>
    </Animated.View>
  );
}

react: 17.0.1 => 17.0.1 react-native: 0.64.2 => 0.64.2 react-native-gesture-handler: 1.10.3 => 1.10.3

Read more comments on GitHub >

github_iconTop Results From Across the Web

Developers - Nested Pan / LongPressGestureHandlers with ...
Nested Pan / LongPressGestureHandlers with different behavior Android / iOS.
Read more >
Hand off parent container's pan gesture to nested ...
To attempt to solve this, I've tried several delegate/protocol solutions in which I detect the position of the divider in the split view...
Read more >
React Native Gesture Handler: Swipe, long-press, and more
Implementing gestures in a React Native app improves the user experience. Learn how to create swipeable, pan, long-press, and other ...
Read more >
Expo Import Of Pressable Throws Invariant Violation Error
In my React Native 0.63.2 app there are 2 nested pan gesture handlers //waitFor{[panRef]} simultaneousHandlers{[ pinchRef. npm add reactnativesvg save. 2. npm ...
Read more >
react-native-gesture-handler: Versions - Openbase
Full version history for react-native-gesture-handler including change logs. ... it used to work when handlers overlap or are "nested" within each other.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found