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.73k stars 317 forks source link

Hopefully an easy question - how do I pack items in carousel together? #557

Open crieggalder opened 7 months ago

crieggalder commented 7 months ago

Hello! Would really appreciate help with a hopefully simple question. :)

Trying to pack carousel items together without space between so that multiple of them show at the same time. Here's my code:

import { Dimensions, View } from 'react-native';
import { Easing } from 'react-native-reanimated';
import Carousel from 'react-native-reanimated-carousel';

export function HomeCampCarouselExperiment() {
    const screenWidth = Dimensions.get('window').width;

    const data = [{}, {}, {}, {}, {}, {}];

    return (
        <Carousel
            height={30}
            width={screenWidth}
            data={data}
            style={{ borderColor: 'red', borderWidth: 1 }}
            renderItem={({ item }) => <View style={{ width: 100, height: '100%', backgroundColor: 'blue' }} />}
            pagingEnabled={false}
            snapEnabled={false}
            autoFillData={true}
            loop
            autoPlay
            withAnimation={{
                type: 'timing',
                config: {
                    duration: 10000,
                    easing: Easing.linear,
                },
            }}
        />
    );
}

Here's the output:

https://github.com/dohooo/react-native-reanimated-carousel/assets/36721640/bd0c7ea9-b573-4f99-9ee4-d79b19192d7e

What I expect is to see multiple blue boxes packed together. How can I achieve that?

97hdz commented 7 months ago

Hello, i think the problem is in the width that you passed on renderItem.

if you want the item ( in this case the blue background ) to use the whole screen and make the effect that all the items are together, you should do something like this:

renderItem={({ item }) => <View style={{ width: '100%', height: '100%', backgroundColor: 'blue' }} />} or renderItem={({ item }) => <View style={{ width: screenWidth, height: '100%', backgroundColor: 'blue' }} />}

crieggalder commented 7 months ago

Hi @97hdz - thank you for the reply! In this case width: 100 is just a placeholder. What I'm really trying to do is let my carousel items take their natural width. Here's a more representative reproduction:

import { Dimensions, Text, View } from 'react-native';
import { Easing } from 'react-native-reanimated';
import Carousel from 'react-native-reanimated-carousel';

export function HomeCampCarouselExperiment() {
    const screenWidth = Dimensions.get('window').width;

    const data = [{ text: 'one' }, { text: 'two' }, { text: 'three' }, { text: 'four' }, { text: 'five' }];

    return (
        <Carousel
            height={30}
            width={screenWidth}
            data={data}
            style={{ borderColor: 'red', borderWidth: 1 }}
            renderItem={({ item }) => (
                <View style={{ height: '100%', borderColor: 'blue', borderWidth: 1 }}>
                    <Text>{item.text}</Text>
                </View>
            )}
            pagingEnabled={false}
            snapEnabled={false}
            autoFillData={true}
            loop
            autoPlay
            withAnimation={{
                type: 'timing',
                config: {
                    duration: 10000,
                    easing: Easing.linear,
                },
            }}
        />
    );
}

https://github.com/dohooo/react-native-reanimated-carousel/assets/36721640/f87bcd11-f224-4514-b798-b8d0fa733227

The problem is that each item is already screen width instead of taking its natural width (the length of the text within). Adding width: screenWidth to the item's parent <View> in renderItem doesn't change this at all.

How can I make each item take its natural width and pack rather than expanding to take the full screen width?

97hdz commented 7 months ago

my bad, if i understand correctly your problem you can try this solution :

Screenshot 2024-03-06 alle 19 28 46
 // -------- this is your screenWidth
const PAGE_WIDTH = styles?.carousel?.width
 //--------- Number of elements that you want to see
  const COUNT = 3;
  const baseOptions =
    ({
      vertical: false,
      width: PAGE_WIDTH / COUNT,
      height: 50,
      style: {
        width: PAGE_WIDTH,
      },
    } as const);
  const data = [{text: 'one'}, {text: 'two'}, {text: 'three'}, {text: 'four'}, {text: 'five'}];

       <Carousel
          {...baseOptions}
          loop
          autoPlay={true}
          autoPlayInterval={1000}
          data={data}
          renderItem={({item}) => (
            <View style={{height: '100%', borderColor: 'blue', borderWidth: 1}}>
              <Text>{item.text}</Text>
            </View>
          )}
        />

if you want to also uniformally set the width inside of the carousel

Screenshot 2024-03-06 alle 19 43 41
<View style={{height: '100%', borderColor: 'blue', borderWidth: 1, width: `50%`}}>

and if you want a particular width for every img/item, you have to specify it, an example:

Screenshot 2024-03-06 alle 19 51 27
const data = [{text: 'one', width: '70%'}, {text: 'two', width: '100%'}, {text: 'three', width: '80%'}, {text: 'four', width: '100%'}, {text: 'five', width: '40%'}];
 <View style={{height: '100%', borderColor: 'blue', borderWidth: 1, width: `${item.width}`}}>
crieggalder commented 6 months ago

Thank you. In my case, the items will be varying widths. Guessing I may have to fire an onLayout state update to track them. Kinda gnarly! Wish there was a way for children to just take their natural sizes

crieggalder commented 6 months ago

@97hdz here is as far as I've gotten--would welcome any help! I think the problem is that the width prop is overriding the View's style={{width: itemWidths[index]}} and forcing each item to be full width. I don't know how I can make the width prop different for each item or not set it so I can set widths for items individually via renderItem (not setting it gives an error).

import { useState } from 'react';
import { Dimensions, Text, View } from 'react-native';
import { Easing } from 'react-native-reanimated';
import Carousel from 'react-native-reanimated-carousel';

export function HomeCampCarouselExperiment() {
    const screenWidth = Dimensions.get('window').width;

    const data = [
        { text: 'one long' },
        { text: 'two' },
        { text: 'three long' },
        { text: 'four' },
        { text: 'five long long' },
        { text: 'six' },
        { text: 'seven' },
        { text: 'eight long long long' },
        { text: 'nine' },
        { text: 'ten' },
    ];

    const [itemWidths, setItemWidths] = useState({});

    return (
        <Carousel
            style={{ width: screenWidth }}
            data={data}
            height={50}
            width={screenWidth}
            renderItem={({ item, index }) => (
                <View
                    style={{ width: itemWidths[index] }}
                    onLayout={(event) => {
                        const { width } = event.nativeEvent.layout;
                        setItemWidths((currentItems) => {
                            const updatedWidths = { ...currentItems };
                            updatedWidths[index] = width;
                            return updatedWidths;
                        });
                    }}
                >
                    <Text>{item.text}</Text>
                </View>
            )}
            pagingEnabled={false}
            snapEnabled={false}
            autoFillData={false}
        />
    );
}
R0LLeX commented 6 months ago

@97hdz I want to achieve this arrangement of elements, where each has a different width

310620654-f1b53d59-010a-4584-9a53-bd5bd6ef3fc5

however, if I do as you suggested, then anyway the width of the carousel overrides the width of the block, and the wrong indentation appears, can you tell me how to solve this?

https://github.com/dohooo/react-native-reanimated-carousel/assets/95383638/ddb95a7d-c963-4c41-88f9-b01f630f326b

const dataCheckPoint = [ { title: "Desired position", icon: "tie", width: 150, }, { title: "Skills", icon: "tools", width: 100, }, { title: "AI questions", icon: "message-circle-question", width: 150, }, { title: "Work experience", icon: "briefcase", width: 150, }, ];

const baseOptions = { vertical: false, width: 150, height: 55, } as const;

97hdz commented 6 months ago

@R0LLeX @crieggalder Hello guys! If you guys want just a simple horizontal-scroll component, there is no need to use an external library, the Scrollview from react-native will do just fine

here's an example :

https://github.com/dohooo/react-native-reanimated-carousel/assets/70217263/bd2a2f21-fbfe-452c-bf30-4d7539c64887

the code :

import React from "react";
import { ScrollView, View } from "react-native";
import FeatureCard from "./FeatureCard";

const Features = () => {
  return (
    <ScrollView
      horizontal
      showsHorizontalScrollIndicator={false}
      className="px-2"
    >
      <FeatureCard title={"Vois sur ton chemin"} />
      <FeatureCard title={"Space Song"} optinalWidth={"w-80"} />
      <FeatureCard title={"Beyond the fire"} optinalWidth={"w-35"} />
      <FeatureCard title={"Incubo"} optinalWidth={"w-50"} />
      <FeatureCard title={"Fantasma"} optinalWidth={"w-22"} />
      <FeatureCard />
    </ScrollView>
  );
};

export default Features;

as you guys can see its really simple, ( the classname are in tailwind so it might seem weird ) in this example i just write the component FeatureCard many times, you can obviously use a map function.

R0LLeX commented 6 months ago

@97hdz I've tried using Scrollview and Flatlist in this way, however I need a hard binding of the active element to the left edge of the screen, that's why I use this library.

tibbe commented 6 months ago

I have the same problem trying to implement the Material Design 3 carousel on top of this library: https://m3.material.io/components/carousel/overview

I want the items to simply take whatever width they are rendered with (with some gap between). However each item seems to be hardcoded to get the width passed to the carousel component itself.