38

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>
Abhishek Ghosh
  • 2,593
  • 3
  • 27
  • 60
Srinivas Guni
  • 2,234
  • 2
  • 16
  • 17

6 Answers6

86

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

Vasil Enchev
  • 1,656
  • 15
  • 18
13

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>
    );
  }
}
phatmann
  • 18,161
  • 7
  • 61
  • 51
3

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

TalP
  • 59
  • 7
2

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

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
Georgios
  • 4,764
  • 35
  • 48
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}
mate
  • 1
  • 1
  • 3