software-mansion / react-native-gesture-handler

Declarative API exposing platform native touch and gesture system to React Native.
https://docs.swmansion.com/react-native-gesture-handler/
MIT License
6.12k stars 980 forks source link

Pan gesture cannot receive event while scrolling #2474

Closed QuangBinhDinh closed 1 year ago

QuangBinhDinh commented 1 year ago

Description

I'm creating a swipeable modal using react-native-gesture-handler and react-native-modal. When open a modal, it only show half of a screen, user can swipe up to expand it to full screen (show more detail). Inside modal I have a ScrollView showing content. When ScrollView is scrolled to top, if user continue to swipe down modal will slowly go down and disappear if the user swipes past a certain threshold. First time implement I recognize that while scrolling PanGesture cannot receive event, which my modal cannot translate. I fix this on IOS by using bounce property, cause contentOffset.y can go below zero and my modal can translate depend on that. But bounce doesn't exist on Android and I still don't have a solution. Here is my Snack repo: https://snack.expo.dev/@quangbinh1999/swipeable-modal

On IOS it's almost perfect (I still don't want to use bounce because it created a black space which looks not nice). On Android I cannot swipe down to close modal while it's full screen, I have to touch outside it. Also for some reason I try to wrap GestureHandlerRootView but it keep getting an error on my Snack. I really appreciate someone can help me fix my code.

Steps to reproduce

Above

Snack or a link to a repository

https://snack.expo.dev/@quangbinh1999/swipeable-modal

Gesture Handler version

2.5.0

React Native version

0.66.4

Platforms

Android, iOS

JavaScript runtime

None

Workflow

None

Architecture

Paper (Old Architecture)

Build type

Debug mode

Device

Android emulator

Device model

No response

Acknowledgements

Yes

j-piasecki commented 1 year ago

In case of the error in the snack, the problem is that GestureHandlerRootView is not a default export, so you would need to change:

import GestureHandlerRootView from 'react-native-gesture-handler';

to

import { GestureHandlerRootView } from 'react-native-gesture-handler';

You could also try using https://github.com/gorhom/react-native-bottom-sheet, as it already handles a lot of edge cases between platforms and could save you a lot of time 😄.

QuangBinhDinh commented 1 year ago

@j-piasecki Thanks for advice. I just try react-native-bottom-sheet and seem like it does not work like I expect. The library doesn't have "skipping snap point" function, which every time I want to close my modal, it stops at the middle and not close immediately. I think I will follow my current code right now, however I just found even wrap GestureHandlerRootView the modal doesn't receive event on Android. Is it because Modal considered outside React Native hierarchy view?

j-piasecki commented 1 year ago

IIRC, Modal creates a separate root, so you would need to wrap the contents of Modal with another GestureHandlerRootView.

QuangBinhDinh commented 1 year ago

@j-piasecki

I'm trying to create a modal/bottom-sheet like this one. If you can come up with any idea I would very appreciate.

https://github.com/software-mansion/react-native-gesture-handler/assets/43164997/ebc1fc34-731a-4dbd-88d9-32481d97a3c2

j-piasecki commented 1 year ago

I think something like this would work:

import React, { useCallback, useMemo, useRef } from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import BottomSheet from '@gorhom/bottom-sheet';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

const App = () => {
  // ref
  const bottomSheetRef = useRef<BottomSheet>(null);

  // variables
  const snapPoints = useMemo(() => ['50%', '100%'], []);

  // callbacks
  const handleSheetChanges = useCallback((from: number, to: number) => {
    console.log('handleAnimate', from, to);

    if (to === 0) {
      bottomSheetRef.current?.forceClose();
    }
  }, []);

  // renders
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <View style={styles.container}>
        <Button
          title="Open"
          onPress={() => {
            bottomSheetRef.current?.snapToIndex(0);
          }}
        />
        <BottomSheet
          ref={bottomSheetRef}
          index={0}
          snapPoints={snapPoints}
          enablePanDownToClose={true}
          onAnimate={handleSheetChanges}>
          <View style={styles.contentContainer}>
            <Text>Awesome 🎉</Text>
          </View>
        </BottomSheet>
      </View>
    </GestureHandlerRootView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    backgroundColor: 'grey',
  },
  contentContainer: {
    flex: 1,
    alignItems: 'center',
  },
});

export default App;
QuangBinhDinh commented 1 year ago

Thanks for your recommend. I finally use BottomSheetModal with some additional props and it works. Close this issue now