dohooo / react-native-reanimated-carousel

🎠 React Native swiper/carousel component, fully implemented using reanimated v2, support to iOS/Android/Web. (Swiper/Carousel)
https://react-native-reanimated-carousel.vercel.app
MIT License
2.71k stars 314 forks source link

horizontal FlatList in a horizontal Carousel component #639

Open JoshTechLee opened 2 months ago

JoshTechLee commented 2 months ago

I have a nested horizontal FlatList within an also horizontal carousel component that will work on iOS but not on Android; when I try scrolling horizontally on the nested FlatList, the carousel moves instead. I've tried using the FlatList from react-native and from react-native-gesture-handler, both with nestedScrollEnabled but none seem to be doing the job.

dosubot[bot] commented 2 months ago

Hey @JoshTechLee! I'm here to help you with any bugs, questions, or contributions. Let's tackle this issue together!

To fix the issue where a nested horizontal FlatList within a horizontal Carousel component scrolls the carousel instead of the FlatList on Android, you can adjust the activeOffsetX of the gesture handler for the Carousel. This will ensure that the x-axis is free and does not impede the scroll of the FlatList.

Here is an example of how you can configure the activeOffsetX:

import * as React from "react";
import type { ICarouselInstance } from "react-native-reanimated-carousel";
import Carousel from "react-native-reanimated-carousel";
import { SafeAreaView } from "react-native-safe-area-context";
import { getImages } from "../../utils/get-images";
import SButton from "../../components/SButton";
import { ElementsText, window } from "../../constants";
import { useWindowDimensions, View, FlatList, StyleSheet } from "react-native";
import Animated, { Extrapolation, interpolate, useSharedValue, useAnimatedStyle } from "react-native-reanimated";
import { GestureHandlerRootView } from "react-native-gesture-handler";

const PAGE_WIDTH = window.width;
const LARGE_IMAGE_WIDTH = PAGE_WIDTH * 0.5;
const MEDIUM_IMAGE_WIDTH = LARGE_IMAGE_WIDTH * 0.5;
const SMALL_IMAGE_WIDTH = MEDIUM_IMAGE_WIDTH * 0.5;
const COUNT = 3;

const data = getImages();

function Index() {
  const windowWidth = useWindowDimensions().width;
  const [isAutoPlay, setIsAutoPlay] = React.useState(false);
  const ref = React.useRef<ICarouselInstance>(null);

  const baseOptions = {
      vertical: false,
      width: windowWidth * 0.45,
      height: PAGE_WIDTH / 1.5,
      style: {
        width: PAGE_WIDTH,
      }
    } as const;

  const scrollX = useSharedValue<number>(0);

  const onScroll = (offsetProgress: number) => {
    scrollX.value = offsetProgress * -1;
  };

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <SafeAreaView edges={["bottom"]} style={{ flex: 1 }}>
        <Carousel
          {...baseOptions}
          loop
          enabled
          ref={ref}
          autoPlay={isAutoPlay}
          data={data}
          onProgressChange={onScroll}
          renderItem={({ index, item }: any) => (
            <Item
              id={index}
              img={item}
              scrollX={scrollX}
            />
          )}
          onConfigurePanGesture={gestureChain => (
            gestureChain.activeOffsetX([-10, 10])
          )}
        />
        <View style={{ flex: 1 }}>
          <SButton
            onPress={() => {
              setIsAutoPlay(!isAutoPlay);
            }}
          >
            {ElementsText.AUTOPLAY}:{`${isAutoPlay}`}
          </SButton>
          <SButton
            onPress={() => {
              ref.current?.scrollTo({ count: -1, animated: true });
            }}
          >
            prev
          </SButton>
          <SButton
            onPress={() => {
              ref.current?.scrollTo({ count: 1, animated: true });
            }}
          >
            next
          </SButton>
        </View>
      </SafeAreaView>
    </GestureHandlerRootView>
  );
}

const Item = ({ id, img, scrollX }: { id: number, img: ImageSourcePropType, scrollX: any }) => {
    const inputRange = [
        (id - 2) * SMALL_IMAGE_WIDTH, 
        (id - 1) * SMALL_IMAGE_WIDTH,
        id * SMALL_IMAGE_WIDTH, 
        (id + 1) * SMALL_IMAGE_WIDTH
    ];

    const outputRange = [
        SMALL_IMAGE_WIDTH,
        MEDIUM_IMAGE_WIDTH,
        LARGE_IMAGE_WIDTH,
        SMALL_IMAGE_WIDTH
    ];

    const animatedStyle = useAnimatedStyle(() => ({
        width: interpolate(scrollX.value, inputRange, outputRange, Extrapolation.CLAMP)
    }));

  return (
    <View style={styles.container}>
        <Animated.Image 
            source={img} 
            style={[styles.image, animatedStyle]} 
        />
    </View>
    );
};

const styles = StyleSheet.create({
    container: {
      marginVertical: 10,
    },
    image: {
      width: PAGE_WIDTH,
      height: 250,
      borderRadius: 20,
      marginRight: 10
    }
});

export default Index;

This configuration will help in ensuring that the FlatList scrolls correctly within the Carousel. Additionally, wrapping the entire component in a GestureHandlerRootView ensures that gesture handling is properly managed [1][2][3].

To continue talking to Dosu, mention @dosu.

idylmz commented 1 month ago

In addition to bots comment, here is how I fixed it:

    <GestureHandlerRootView style={styles.container}>
      <ThemedView style={styles.container} noPadding>
        <Carousel
          defaultIndex={1}
          loop={false}
          width={width}
          height={height}
          data={dates}
          onSnapToItem={onIndexChanged}
          panGestureHandlerProps={{
            activeOffsetX: [-20, 20],
          }}
          renderItem={({ item }) => (
            <TransactionList date={item} key={item.getTime()} />
          )}
        />
      </ThemedView>
    </GestureHandlerRootView>

I don't know which version does the bot refer to but I have found this

panGestureHandlerProps={{
            activeOffsetX: [-20, 20],
          }}

instead of this one here.

onConfigurePanGesture={gestureChain => (
            gestureChain.activeOffsetX([-10, 10])
          )}
csulit commented 3 weeks ago

panGestureHandlerProps={{ activeOffsetX: [-20, 20], }}

This one works

panGestureHandlerProps={{
            activeOffsetX: [-20, 20],
          }}