0

I'm building a bottom-sheet that one can swipe up/down to change its height. I want its inner children component to be scrollable, but it doesnt work as expected. I'm just guessing the cause of this is that any touch event like scrolling in ScrollView gets captured and bubbled up to GestureDetector so scrolling the inner content doesnt work while swiping the sheet itself works.

import { Dimensions, StyleSheet, TouchableOpacity } from 'react-native';
import React, { useCallback, useEffect } from 'react';
import Animated from 'react-native-reanimated';
import {
  Gesture,
  GestureDetector,
  ScrollView,
} from 'react-native-gesture-handler';
import {
  Extrapolate,
  interpolate,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
} from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useHeaderHeight } from '@react-navigation/elements';

const { height: SCREEN_HEIGHT } = Dimensions.get('window');

type Props = {
  top: number;
  bottom: number;
  initialBottomOffset: number;
};

export const BottomSheet: React.FC<Props> = ({
  children,
  top,
  bottom,
  initialBottomOffset,
}) => {
  const translateY = useSharedValue(0);
  const insets = useSafeAreaInsets();
  const HEADER_HIGHT = useHeaderHeight();

  const MAX_TRANSLATE_Y = -SCREEN_HEIGHT + top;
  const MIN_TRANSLATE_Y = -(bottom + insets.bottom);

  const scrollTo = useCallback((destination: number) => {
    'worklet';
    translateY.value = withSpring(destination, { damping: 50 });
  }, []);

  const context = useSharedValue({ y: 0 });
  const gesture = Gesture.Pan()
    .onStart(() => {
      context.value = { y: translateY.value };
    })
    .onUpdate(event => {
      translateY.value = event.translationY + context.value.y;
      translateY.value =
        event.translationY < 0
          ? Math.max(translateY.value, MAX_TRANSLATE_Y)
          : Math.min(translateY.value, MIN_TRANSLATE_Y);

      if (Math.abs(event.velocityY) > 300) {
        if (event.translationY < 0) {
          scrollTo(MAX_TRANSLATE_Y);
        } else {
          scrollTo(MIN_TRANSLATE_Y);
        }
      }
    });

  const animatedBottomSheetStyle = useAnimatedStyle(() => {
    const borderRadius = interpolate(
      translateY.value,
      [MAX_TRANSLATE_Y + HEADER_HIGHT, MAX_TRANSLATE_Y],
      [25, 0],
      Extrapolate.CLAMP,
    );

    return {
      borderRadius,
      transform: [{ translateY: translateY.value }],
    };
  });

  useEffect(() => {
    scrollTo(MIN_TRANSLATE_Y);
  }, [bottom]);

  useEffect(() => {
    scrollTo(-initialBottomOffset);
  }, [initialBottomOffset]);

  return (
    <GestureDetector gesture={gesture}>
      <Animated.View
        style={[styles.bottomSheetContainer, animatedBottomSheetStyle]}>
        <TouchableOpacity
          style={styles.handle}
          onPress={() => scrollTo(MAX_TRANSLATE_Y)}
        />
        <ScrollView>{children}</ScrollView>
      </Animated.View>
    </GestureDetector>
  );
};

const styles = StyleSheet.create({
  bottomSheetContainer: {
    position: 'absolute',
    top: SCREEN_HEIGHT,
    width: '100%',
    height: '100%',
    backgroundColor: '#fff',
    borderRadius: 25,
    flex: 1,
  },
  handle: {
    width: 45,
    height: 5,
    backgroundColor: '#CEDEEA',
    alignSelf: 'center',
    marginVertical: 10,
    borderRadius: 4,
  },
});

enter image description here

Otani Shuzo
  • 1,118
  • 1
  • 11
  • 22

1 Answers1

2

It is working fine after ScrollView & Flatlist are imported in react-native-gasture-handler

Abhishek Dutt
  • 1,308
  • 7
  • 14
  • 24