react-native-snap-carousel icon indicating copy to clipboard operation
react-native-snap-carousel copied to clipboard

Multiple active items in each screen

Open mazing opened this issue 7 years ago • 46 comments

Would it be possible to have multiple items at one slide just like Apple Music:

screen shot 2017-07-26 at 11 38 35

Would the right approach be to change data such that all elements are grouped with two elements in each group and then print the two elements side-by-side when rendering each item?

However, Airbnb has the same feature where no items are active, but you can only scroll such that two elements are always visible. Can the same behaviour be achieved with the right size options with react-native-snap-carousel?

screen shot 2017-07-26 at 12 47 39

mazing avatar Jul 26 '17 09:07 mazing

Regarding your first question, having multiple acting items at once is possible but would require some heavy refactoring. I don't have time to work on it at the moment, but I'll keep that in mind for future releases.

And yes, as a workaround you can group two elements in one slide to emulate the behavior, if:

  • the special index handling is not a no-go for you
  • you're ok with snapping two slides every time you move.

Otherwise, I confirm that you can easily reproduce the second layout. Here is a quick example, using the FlatList version (for the activeSlideAlignment prop):

react-native-snap-carousel example

const { width: viewportWidth, height: viewportHeight } = Dimensions.get('window');
const SLIDE_WIDTH = Math.round(viewportWidth / 2.6);
const ITEM_HORIZONTAL_MARGIN = 15;
const ITEM_WIDTH = SLIDE_WIDTH + ITEM_HORIZONTAL_MARGIN * 2;
const SLIDER_WIDTH = viewportWidth;

<Carousel
  sliderWidth={SLIDER_WIDTH}
  itemWidth={ITEM_WIDTH}
  activeSlideAlignment={'start'}
  inactiveSlideScale={1}
  inactiveSlideOpacity={1}
/>

bd-arc avatar Jul 26 '17 13:07 bd-arc

Awesome! Thanks! Great library :-D

However, it seems if I have uneven number of items, I can swipe to a non-existing item such that, if I scroll as much as I can to the right, the last item is in the left of the screen and a non-existing item takes up the right part of the screen.

mazing avatar Jul 26 '17 17:07 mazing

If you're talking about your first idea (2 slides in one), that's to be expected since your itemWidth value is twice the real item's width.

Don't forget that you're "hacking" the plugin ;-)

bd-arc avatar Jul 27 '17 07:07 bd-arc

is there any known reason why on my app all items are look active even I set inactiveSlide values? Like this

    <Carousel
            data={this.props.items.toArray()}
            renderItem={this.renderItem}
            contentContainerCustomStyle={{  alignItems: 'center' }}
            inactiveSlideScale={0.8}
            inactiveSlideOpacity={0.7}
            sliderWidth={this.state.sliderWidth}
            itemWidth={this.state.itemWidth}
            enableSnap={true}
            loop={true}
            lockScrollWhileSnapping={true}
            autoplay={true}
            autoplayDelay={500}
            autoplayInterval={3000}
            />

devken-net avatar Oct 20 '17 08:10 devken-net

@markortiz What is your React Native version? If it is not one of the latest, see #173 and particularly this comment.

bd-arc avatar Oct 20 '17 08:10 bd-arc

@bd-arc i'm currently using "react-native": "0.44.0", base on your explanation. I see the issue now. But on 3.1.0 infinite loop is not yet supported right?

devken-net avatar Oct 20 '17 09:10 devken-net

Hi @markortiz,

I didn't realize you've edited your message. Yes, you're right: the loop feature has been released in version 3.3.0.

You have a choice to make:

  • stay with 3.1.0 to get item's animation
  • update to 3.3.0+ to get the loop feature
  • upgrade your RN version to get both.

No easy path here, sorry.

bd-arc avatar Oct 22 '17 16:10 bd-arc

@bd-arc How I can solve this problem?

However, it seems if I have uneven number of items, I can swipe to a non-existing item such that, if I scroll as much as I can to the right, the last item is in the left of the screen and a non-existing item takes up the right part of the screen.

simulator screen shot - iphone 6 - 2017-11-08 at 16 45 05

donnes avatar Nov 08 '17 18:11 donnes

Hi @donnes,

I'm going to need more info to help you with that. Can you please open a new issue and make sure to follow the contributing guidelines?

bd-arc avatar Nov 08 '17 18:11 bd-arc

Hi @bd-arc I think is not necessary create a new issue, because the problem is related to this issue.

I create a reproduce Snack to be easy to find the problem, here's the link: https://snack.expo.io/ryxxwAeJG

As @mazing said earlier, following your example (https://github.com/archriss/react-native-snap-carousel/issues/102#issuecomment-318061156) it's will generate a blank space at end of the scroll. If you run the exemple above and scroll to right you'll can see a blank space.

I don't know how solve this problem, I tried many things but can't find a solution.

donnes avatar Nov 08 '17 19:11 donnes

@donnes I see what you mean. I didn't provide the full example at that time and I now realize that the code from my previous answer can lead to issues like yours.

I've modified the Snack you provided to make it work as expected: https://snack.expo.io/Hynr0n-1z

They were two problems:

  • sliderItemHorizontalMargin needed to be added as paddingHorizontal instead of marginHorizontal since the latter would modify item's length and completely mess with the plugin (this explains the fact that the view was scrolling a bit more to the right each snap, and ended up with a large "blank" section).
  • an outer (yellow) and inner (purple) containers were needed in order for the margin to be properly applied.

Note that plugin's official example implements those two suggestions.

I'll update the relevant doc section to the following:

const horizontalMargin = 20;
const slideWidth = 280;

const sliderWidth = Dimensions.get('window').width;
const itemWidth = slideWidth + horizontalMargin * 2;
const itemHeight = 200;

const styles = Stylesheet.create({
    slide: {
        width: itemWidth,
        height: itemHeight,
        paddingHorizontal: horizontalMargin
        // other styles for your item's container
    },
    slideInnerContainer: {
        width: slideWidth,
        flex: 1
        // other styles for the visible container
    }
};

Do you think it will make things clear enough?

bd-arc avatar Nov 09 '17 11:11 bd-arc

@bd-arc I believe I did not explain the issue I was passing correctly.

Below is an example of the Carousel on the Airbnb app

airbnb-example

And now the example I sent earlier (https://snack.expo.io/BJ4_VmS1M)

snap-carousel-example

Looking at the examples that I sent you can notice that in the second gif, when the scroll comes to the end, there is a red space as if there was a "ghost" Card.

I believe that the correct behavior is when the scroll reaches the end, the scroll does not exceed the last Card. Some like this:

untitled

donnes avatar Nov 12 '17 01:11 donnes

@donnes Ah, I now see what you mean!

To be honest, this can prove tough to implement because it would require modifying ScrollView's width and a bunch of the associated logic when activeSlideAlignment is set to either start or end.

But I'll make sure to take a look at it, as I agree that this would be better from a user experience point of view ;-)

bd-arc avatar Nov 13 '17 08:11 bd-arc

@bd-arc I'll create a fork and try take a look about it. Thank you for the good job! :D

donnes avatar Nov 13 '17 10:11 donnes

@donnes I've just played with the source code to see what I would be able to do about it, but I unfortunately don't think that it can be implemented at the moment.

Currently, we determine the active item when its position meets an imaginary line linked to the value of activeSlideAlignment. For example, with a start value this line is at itemWidth / 2. If we're not able to scroll to this position, the last items will never be seen as active, impairing the entire plugin logic.

The VirtualizedList component provides a scrollToIndex() method that accepts two interesting options: viewPosition and viewOffset. I've previously tried using those as they were supposed to allow the kind of behavior you're after. Unfortunately, they have major drawbacks:

  • viewPosition is buggy as hell
  • viewOffset doesn't work on Android
  • scrollToIndex() is a no-go for now (see this React Native issue).

If you have any idea about how to implement it, I'll be glad to hear your thoughts. Because it seems that I'm kind of stuck until Facebook improves the underlying components (by the way, you can vote to let their team know about it ;-)).

bd-arc avatar Nov 13 '17 10:11 bd-arc

@bd-arc I had previously tried implemented myself carousel component using FlatList and I got these problems too. Aparently the only way is use ScrollView (for IOs) and ViewPagerAndroid (for Android) but yet I got some problems with the latest versions of React Native (0.50+).

By the way, I'll stud the repo code and try some solutions and I back if find something. And also, I'm going to vote for these features as you are suggested.

Again, thank you for the good job! 👍

donnes avatar Nov 13 '17 12:11 donnes

@donnes While the plugin is based on <FlatList />, it is nothing more than a <ScrollView /> in the end ;-)

The ViewPagerAndroid component has been heavily considered since it behaves so much better than the regular ScrollView on Android. Unfortunately, the preview effect (which is the root of the plugin) can't be implemented with it at the moment. A prop peekEnabled has recently been added to it, and our expectations were high, but it just doesn't work...

As you can see, I've tried a lot of things to improve the plugin, but RN's core always gets in the way. I'll be pretty glad if you manage to find a solution :-)

bd-arc avatar Nov 13 '17 12:11 bd-arc

You can style Carousel with prop contentContainerCustomStyle contentContainerCustomStyle={{overflow: 'hidden', width: widthItemSlide * (numberSlide)}} Everything will be Okay

phithu avatar Jan 15 '18 08:01 phithu

Yeah, it's a huge miss in my opinion too. Amazing module but in this situation (multi elements) the space totally kills the user interface.

univers3 avatar Feb 26 '18 15:02 univers3

@univers3 I heartily agree with you and I'd really like to be able to implement it this way.

But if you've read this comment, you already know that this is no piece of cake and that the horde of React Native bugs add another layer of complexity...

bd-arc avatar Feb 26 '18 16:02 bd-arc

@phithu Amazing! Although it's not an implementation in the module but instead just limiting the width, it works perfectly in my case.

For reference, this is what I did:

<Carousel
    data={this.props.data}
    renderItem={this._renderItem}
    itemWidth={100}
    activeSlideAlignment={'start'}
    {/** Other props here... */}
    contentContainerCustomStyle={{
        overflow: 'hidden',
        width: 100 * this.props.data.length
    }}
/>

FoxInFlame avatar Apr 29 '19 08:04 FoxInFlame

thank you @FoxInFlame @phithu! Wanted to note that on Android 9 devices when only SINGLE item Carousel is broken by setting width in contentContainerCustomStyle leading to empty Carousel. So I don't set width in this case

const NUMBER_WHICH_CAUSES_BUG_ON_ANDROID_9 = 1;
contentContainerCustomStyle={{
            overflow: 'hidden',
            width:
            // workaround for case when 1 item 
              connectedItems.length === NUMBER_WHICH_CAUSES_BUG_ON_ANDROID_9
                ? undefined
                : this.itemWidth * connectedItems.length,
          }}

CyxouD avatar Oct 17 '19 14:10 CyxouD

@bd-arc How I can solve this problem?

However, it seems if I have uneven number of items, I can swipe to a non-existing item such that, if I scroll as much as I can to the right, the last item is in the left of the screen and a non-existing item takes up the right part of the screen.

simulator screen shot - iphone 6 - 2017-11-08 at 16 45 05

I'm just using snap-carousel recently and come across this issue as well for the 'right empty space'. I got the solution but require to add some additional code in Carousel.js First we need to add an additional prop lastItemOffsetWidth: PropTypes.number to know the remaining width on the right we want e.g. margin 10 or 20.

lastly, in function:

_initPositionsAndInterpolators (props = this.props) {
        const { data, itemWidth, itemHeight, scrollInterpolator, vertical, activeSlideAlignment, loop, sliderWidth, lastItemOffsetWidth } = props;
       ...
        this._getCustomData(props).forEach((itemData, index) => {
            ...
			if(this._getCustomData(props).length-1 == index && activeSlideAlignment=='start' && !loop){
				var offset=lastItemOffsetWidth;
				if(offset==undefined) offset=0;
				this._positions[index] = {
					start: index * sizeRef - (sliderWidth - (sizeRef + offset)),
					end: index * sizeRef - (sliderWidth - (sizeRef + offset)) + sizeRef
				};
			}else{
				this._positions[index] = {
					start: index * sizeRef,
					end: index * sizeRef + sizeRef
				};
			}
...

should do the trick.

t-vc avatar Nov 01 '19 15:11 t-vc

Hi @t-vc,

Thanks for sharing your idea!

Will it work with all values of activeSlideAlignment (start, center, end)? If yes, do you mind creating a PR for that and share a quick video of your code in action?

bd-arc avatar Nov 04 '19 10:11 bd-arc

Do you have any update on this? I was looking for a solution also

kristoff2016 avatar Nov 24 '19 04:11 kristoff2016

Hi, sorry i didn't realise a reply until @kristoff2016 recent comment. @bd-arc I focus only for activeSlideAlignment start to avoid crashing the others. I will share it to you guys after 3rd December as currently I can't access my files until date.

t-vc avatar Nov 24 '19 05:11 t-vc

@t-vc I hope you can share the solution how can you remove the right white space. thanks in advance :)

kristoff2016 avatar Nov 24 '19 05:11 kristoff2016

if(this._getCustomData(props).length-1 == index && activeSlideAlignment=='start' && !loop){
	var offset=lastItemOffsetWidth;
	if(offset==undefined) offset=0;
	this._positions[index] = {
		start: index * sizeRef - (sliderWidth - (sizeRef + offset)),
		end: index * sizeRef - (sliderWidth - (sizeRef + offset)) + sizeRef
	};
}else{
	this._positions[index] = {
		start: index * sizeRef,
		end: index * sizeRef + sizeRef
	};
}

if u notice the original code, this._position[index] is actually calculating each item (start from first to last) snap point, i just add an IF statement to change the last item's snap point, that's all.

t-vc avatar Nov 24 '19 05:11 t-vc

thanks for sharing which file change that I should change or do it? or go to change in the original file in this library? could you help advice me? thank you in advance

kristoff2016 avatar Nov 24 '19 05:11 kristoff2016

just modify the original file Carousel.js

t-vc avatar Nov 24 '19 05:11 t-vc