quidone / react-native-wheel-picker

Wheel Picker for React Native
MIT License
14 stars 3 forks source link

Did not work when Animated View with Panhandler is above #3

Open akucharczyk opened 1 month ago

akucharczyk commented 1 month ago

I have an BottomSheetComponent which the wheelpicker should work in.

import React, { useRef } from 'react'

import {
  Animated,
  Modal,
  PanResponder,
  View,
  LayoutChangeEvent,
  KeyboardAvoidingView
} from 'react-native'

import { useSafeAreaInsets } from 'react-native-safe-area-context'

const BottomSheet: React.FC<{
  visible: boolean;
  onDismiss?: () => void;
  showCloseButton?: boolean;
  children: React.ReactNode;
}> = ({ visible, onDismiss, showCloseButton = false, children }) => {
  const { bottom } = useSafeAreaInsets()
  const translateY = useRef(new Animated.Value(0)).current
  const contentHeight = useRef(0)

  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onPanResponderMove: (_, gestureState) => {
      // console.log('gestureState', gestureState.dy, contentHeight.current, _)
      if (!onDismiss) return

      const newTranslateY = gestureState.dy + contentHeight.current
      if (newTranslateY >= 0 && newTranslateY >= contentHeight.current) {
        translateY.setValue(gestureState.dy)
      }
    },
    onPanResponderRelease: (_, gestureState) => {
      if (!onDismiss) return

      if (gestureState.dy > 50) {
        onDismiss()
        translateY.setValue(gestureState.dy)
        // Wait for the animation to finish before resetting the value
        // to avoid a flickering effect
        setTimeout(() => {
          translateY.setValue(0)
        }, 300)
      } else {
        Animated.spring(translateY, {
          toValue: 0,
          useNativeDriver: true
        }).start()

        if (gestureState.dy > 0) {
          translateY.setValue(gestureState.dy)
        }
      }
    }
  })

  const handleContentLayout = (event: LayoutChangeEvent) => {
    contentHeight.current = event.nativeEvent.layout.height
  }

  return (
    <Modal
      visible={visible}
      transparent
      statusBarTranslucent
      animationType="slide"
      onRequestClose={onDismiss}>
      <KeyboardAvoidingView
        behavior={'padding'}
        className="flex-1"
      >
        <View onPress={onDismiss} className="flex-1 justify-end bg-black/70">
          <Animated.View
            style={[
              {
                transform: [{ translateY }]
              }
            ]}
            className="bg-white rounded-t-3xl elevation-5 max-h-screen max-w-md self-center w-full"
            {...panResponder.panHandlers}
          >
            <>
              {!!onDismiss &&
                <View
                  className="m-4 w-20 h-1.5 bg-black rounded-full self-center"
                  {...panResponder.panHandlers}
                />
              }
              <View
                style={{
                  paddingBottom: bottom
                }}
                className="mt-4 max-w-lg w-full mx-auto"
                onLayout={handleContentLayout}>
                {children}
              </View>
            </>
          </Animated.View>
        </View>
      </KeyboardAvoidingView>
    </Modal>
  )
}

export default BottomSheet

The Children is pretty simple like:

...
<View>
        <WheelPicker
            value={hours}
            data={
              Array.from({ length: 24 }, (_, i) => ({
                label: i.toString(),
                value: i
              }))
            }
            onValueChanged={({ item: { value } }) => setHours(value)}
          />
</View>

I am not able to control the wheelpicker.

rozhkovs commented 1 month ago

Hi @akucharczyk!

It seems to me that there may be a gesture conflict between PanResponder and Animated.ScrollView from the "react-native-reanimated". It is still unclear how this can be solved easily. It is necessary to think about how to coordinate gestures.

If you delete the line of code {...panResponder.panHandlers}, will WheelPicker work?