Horizontal scrollview snapping react native
Asked Answered
C

6

42

Hi I am trying to achieve scrollview snap to center like below gif link

Check This Gif

But unable to do so. Following is my react native code to achieve this.

or is there any method to scroll to particular index of scrollview elements like android ?

Any help would be appreciated. Thanks in advance

<ScrollView
  style={[styles.imgContainer,{backgroundColor:colorBg,paddingLeft:20}]}
  automaticallyAdjustInsets={false}
  horizontal={true}
  pagingEnabled={true}
  scrollEnabled={true}
  decelerationRate={0}
  snapToAlignment='center'
  snapToInterval={DEVICE_WIDTH-100}
  scrollEventThrottle={16}
  onScroll={(event) => {
    var contentOffsetX=event.nativeEvent.contentOffset.x;
    var contentOffsetY=event.nativeEvent.contentOffset.y;

    var  cellWidth = (DEVICE_WIDTH-100).toFixed(2);
    var cellHeight=(DEVICE_HEIGHT-200).toFixed(2);

    var  cellIndex = Math.floor(contentOffsetX/ cellWidth);

    // Round to the next cell if the scrolling will stop over halfway to the next cell.
    if ((contentOffsetX- (Math.floor(contentOffsetX / cellWidth) * cellWidth)) > cellWidth) {
      cellIndex++;
    }

    // Adjust stopping point to exact beginning of cell.
    contentOffsetX = cellIndex * cellWidth;
    contentOffsetY= cellIndex * cellHeight;

    event.nativeEvent.contentOffsetX=contentOffsetX;
    event.nativeEvent.contentOffsetY=contentOffsetY;

    // this.setState({contentOffsetX:contentOffsetX,contentOffsetY:contentOffsetY});
    console.log('cellIndex:'+cellIndex);

    console.log("contentOffsetX:"+contentOffsetX);
      // contentOffset={{x:this.state.contentOffsetX,y:0}}
  }}
>
  {rows}

</ScrollView>
Clance answered 4/10, 2016 at 10:12 Comment(0)
O
91

You don't need other libraries you can do that with ScrollView. All you need is to add the following props in your component.

    horizontal= {true}
    decelerationRate={0}
    snapToInterval={200} //your element width
    snapToAlignment={"center"}

Check this snack for more details on how to implement it https://snack.expo.io/H1CnjIeDb

Outnumber answered 3/8, 2017 at 8:31 Comment(6)
Works with ListView too which is more performant, because it has all of ScrollView's props.Sunglass
what would be the interval if I have a header of X units and margin of Y units as well.Roubaix
interval only cares about width, you can get the device width using the Dimension library const { width } = Dimensions.get('window'); And then just apply all dimensions including margins and what not to the snapToInterval like so snapToInterval={width - 60} Check the expo above for more detailsOutnumber
@William I thought FlatList was generally preferred over ListView?Tenon
@Tenon Yeah good spot, I meant FlatList, always get the damn things confused.Sunglass
The accepted answer is still the best solution so far.Agler
S
14

Use the pagingEnabled property in ScrollView.

const screenHeight = Dimensions.get('window').height;

class Screen extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <ScrollView pagingEnabled>
        <Page1 style={{height: screenHeight}} />
        <Page2 style={{height: screenHeight}} />
      </ScrollView>
    );
  }
}
Swihart answered 15/1, 2019 at 5:23 Comment(1)
best and simplest solution today for horizontal!Blanketyblank
I
4

If you don't want to use snapToInterval because of the misalignment for long lists you can use snapToOffsets which is more precise.

for example:

const { width } = Dimensions.get('window');
this.IMAGE_WIDTH = width * (1 / 2.5)
this.image_margin = 5
this.nishhar = width - ((this.IMAGE_WIDTH + this.image_margin) * 2 + this.image_margin * 2)

dataNum = [1, 2, 3, 4, 5, 6]

return (<FlatList
            data={dataNum}
            renderItem={item => {
                return (
                    <View style={{
                        backgroundColor: item.index % 2 == 0 ? 'red' : 'blue',
                        width: this.IMAGE_WIDTH,
                        height: this.IMAGE_WIDTH,
                        marginLeft: this.image_margin,
                        marginRight: this.image_margin,
                    }}>
                    </View>)
            }}
            keyExtractor={this.keyGenerator}
            horizontal={true}
            snapToAlignment={"start"}
            snapToOffsets={[...Array(dataNum.length)].map((x, i) => (i * (this.IMAGE_WIDTH + 2 * this.image_margin) - (this.nishhar * 0.5)))}
            decelerationRate={"fast"}
            pagingEnabled
        />)

or you can also checkout this example: https://snack.expo.io/SyWXFqDAB

Initiate answered 18/12, 2019 at 12:26 Comment(0)
P
3

Try using the snapToOffsets prop. This requires a simple addition and multiplication though.

To create a snapping effect showing 2 elements, and 10pts from left and right elements that are out of bound;

// import useWindowDimensions from 'react-native'
const { width: windowWidth } = useWindowDimensions();

// the width of a card in ScrollView
// 20 is so out of bound cards on left & right can have 10pts visible;
const cardWidth = (windowWidth / 2) - 20;

// gap between each cards;
const gap = 16;

const halfGap = gap / 2;

const cardContent = [1, 2, 3, 4, 5]

// THIS IS THE FUN PART
const snapOffsets = cardContent
      .map((_, index) => {            
        return (cardWidth * index) + (halfGap * index)
      })

// add left and right margin to <Card/> for gap effect,
// as well as width using cardWidth above 
// conditional left margin to first card, and right margin to last card

return (
   <ScrollView
     horizontal={true}
     snapToOffsets={snapOffsets}
   >
     { 
       cardContent.map((item, index, arr) => {
         return (
           <Card 
             key={index} 
             title={item} 
             style={{
               width: cardWidth,
               marginLeft: index == 0 ? gap : 0,
               marginRight: index == arr.length - 1 ? gap : halfGap
             }}
           />
         )
       })
     }
   </ScrollView>
)

That's it. You can refactor for vertical mode.

For a better edge to edge effect, make sure that ancestor containers don't have padding

Pink answered 3/1, 2022 at 15:17 Comment(0)
C
1

There are several options. Here are two that I've tried and work fine. I prefer the second one because as its doc says "like ListView, this can render hundreds of pages without performance issue".

  1. react-native-page-swiper
  2. react-native-viewpager
Clardy answered 5/10, 2016 at 1:41 Comment(0)
N
0

I had problem with FlatList's snapToInterval logic on iOS (on horizontal list it didn't always snapped correctly to start) - I fixed that with increasing values of below properties (I had single item slider)

maxToRenderPerBatch={6}
initialNumToRender={6}
windowSize={6}
Nickey answered 10/9, 2021 at 9:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.