javascriptandroidiosreact-nativeflatlist

React Native Touch Through Flatlist


For "react-native": "^0.70.5"

Requirement:

Issues with some possible approaches

  1. pointerEvents="none" doesn't work with Flatlist, as internally how Flatlist is built it will block the events at all values of pointerEvents. It's the same with Scrollview as well.
  2. react-native-touch-through-view (the exact library I need) doesn't work with RN 0.70.2, library is outdated. After fixing the build issues, touch events are not propagating to the clickable elements.
  3. Created a custom component ScrollableView, as pointerEvents with View work well. With this adding pointerEvents to none on parts of the children, lets the touch event to propagate to elements below.
import React, { useState, useRef } from 'react';
import { View, PanResponder, Animated } from 'react-native';

const ScrollableView = ({children, style, onScroll}) => {
    const scrollY = useRef(new Animated.Value(0)).current;
    const lastScrollY = useRef(0);
    const scrollYClamped = Animated.diffClamp(scrollY, 0, 1000);

    const panResponder = useRef(
        PanResponder.create({
            onStartShouldSetPanResponder: () => true,
            onPanResponderMove: (_, gestureState) => {
                scrollY.setValue(lastScrollY.current + gestureState.dy);
            },
            onPanResponderRelease: (_, { vy, dy }) => {
                lastScrollY.current += dy;
                Animated.spring(scrollY, {
                    toValue: lastScrollY.current,
                    velocity: vy,
                    tension: 2,
                    friction: 8,
                    useNativeDriver: false,
                }).start();
            },

        })
    ).current;

    const combinedStyle = [
        {
            transform: [{ translateY: scrollYClamped }],
        },
        style
    ];

    return (
        <Animated.View
            {...panResponder.panHandlers}
            pointerEvents="box-none"
            style={combinedStyle}
        >
            {children}
        </Animated.View>
    );
};

export default ScrollableView;

Any solution to any of the above three approaches is appreciated.


Solution

  • https://ui.gorhom.dev/components/bottom-sheet/components/bottomsheetflatlist

    import React, { useCallback, useRef, useMemo } from "react";
    import { StyleSheet, View, Text, Button } from "react-native";
    import BottomSheet, { BottomSheetFlatList } from "@gorhom/bottom-sheet";
    
    const App = () => {
      // hooks
      const sheetRef = useRef<BottomSheet>(null);
    
      // variables
      const data = useMemo(
        () =>
          Array(50)
            .fill(0)
            .map((_, index) => `index-${index}`),
        []
      );
      const snapPoints = useMemo(() => ["25%", "50%", "90%"], []);
    
      // callbacks
      const handleSheetChange = useCallback((index) => {
        console.log("handleSheetChange", index);
      }, []);
      const handleSnapPress = useCallback((index) => {
        sheetRef.current?.snapToIndex(index);
      }, []);
      const handleClosePress = useCallback(() => {
        sheetRef.current?.close();
      }, []);
    
      // render
      const renderItem = useCallback(
        ({ item }) => (
          <View style={styles.itemContainer}>
            <Text>{item}</Text>
          </View>
        ),
        []
      );
      return (
        <View style={styles.container}>
          <Button title="Snap To 90%" onPress={() => handleSnapPress(2)} />
          <Button title="Snap To 50%" onPress={() => handleSnapPress(1)} />
          <Button title="Snap To 25%" onPress={() => handleSnapPress(0)} />
          <Button title="Close" onPress={() => handleClosePress()} />
          <BottomSheet
            ref={sheetRef}
            snapPoints={snapPoints}
            onChange={handleSheetChange}
          >
            <BottomSheetFlatList
              data={data}
              keyExtractor={(i) => i}
              renderItem={renderItem}
              contentContainerStyle={styles.contentContainer}
            />
          </BottomSheet>
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        paddingTop: 200,
      },
      contentContainer: {
        backgroundColor: "white",
      },
      itemContainer: {
        padding: 6,
        margin: 6,
        backgroundColor: "#eee",
      },
    });
    
    export default App;