Make only a specific part to cause scroll movement in scrollview

Viewed 510

To summarize what I am trying to accomplish I sketched this diagram: Diagramm scrollview draggable I am trying to build a custom navigation. In this navigation, I have a main page. Above this main page there sits another page, not visible to the user yet (as the main page fills the whole screen). The main page has scrollable content on it.
There is one component at the top of the main page, that the user can drag to swipe the other/upper page down.

What I have tried so far to accomplish this

I already have an implementation, that works but has some drawbacks.

  • The most outer ScrollView has scrollEnabled=false
  • The draggable component is an Animated.View to which I applied a PanResponder to
  • Once the user clicks and drags the draggable component down, the PanResponder captures this movement
  • I use the captured movement and do outerScrollView.scrollTo(...) --> on iOS this works fine but on android I see some small jittering during the scroll movement.
The code:
interface Props extends ScrollViewProps {
  draggableHeader: ReactNode;
}

const { width, height } = Dimensions.get("window");

export const SwipingNavigator: React.FC<Props> = (props) => {
  const scrollView = useRef<ScrollView>();

  const scrollToMainPage = (animated = true): void =>
    scrollView.current.scrollTo({ x: 0, y: height, animated });

  const scrollToUpperPage = (): void =>
    scrollView.current.scrollTo({
      y: 0,
      x: 0,
      animated: true,
    });

 //when component mounts, scroll to main page
  useEffect(() => {
    scrollToMainPage(false);
  }, []);

  const panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: (e, gestureState) => gestureState.dy > 3,
      //move the content as the user drags the header
      onPanResponderMove: ({ nativeEvent }) => {
        scrollView.current.scrollTo({
          y: height - nativeEvent.pageY + 40,
          x: 0,
          animated: false,
        });
      },
      onPanResponderEnd: ({ nativeEvent }) => {
        //detect to which page the user wants to scroll -> snap effect
        if (nativeEvent.pageY < height / 3) {
          scrollToMainPage();
        } else {
          scrollToUpperPage();
        }
      },
    })
  ).current;

  //assuming child 1 is the main page, child 0 the upper page
  return (
    <ScrollView
      ref={scrollView}
      scrollEnabled={false}
      bounces={false}
      pagingEnabled={true}
      disableScrollViewPanResponder={true}
      showsVerticalScrollIndicator={false}
      {...props}
      style={globalStyles.flex1}>
      {props.children[0]}
      <View style={styles.fs}>
        <Animated.View {...panResponder.panHandlers}>
          {props.draggableHeader}
        </Animated.View>
        {props.children[1]}
      </View>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  fs: {
    flex: 1,
    width,
    height,
  },
});

As already said: the main issue is that it doesn't look that great on android performance-wise + this is probably not the best solution.

Scrolling down from the other page isn't a concern, as it will have a "close"-button.

Other ideas I had

I thought about using the Pan/Gesture-Handler of the draggable component and "inject" this to the ScrollView. So that the scroll view will only detect movements from this panhandler.

I am currently just stuck and I appreciate every new input you can give to me. Thanks in advance for your help :)

// Edit: here is the snack: https://snack.expo.io/@hannojg/surprised-bacon Unfortunately, on expo, it looks butter smooth on android. However, rendering the main page on a real device with RN make the scrolling look different on android.

0 Answers
Related