react-native: FlatList snapToInterval is not precise in long list

Environment

Environment: OS: macOS 10.14 Node: 8.12.0 Yarn: Not Found npm: 6.4.1 Watchman: 4.9.0 Xcode: Not Found Android Studio: 3.2 AI-181.5540.7.32.5014246

Packages: (wanted => installed) react: 16.3.1 => 16.3.1 react-native: https://github.com/expo/react-native/archive/sdk-29.0.0.tar.gz => 0.55.4

Description

I’m using the FlatList component with around 50 elements. When my element height (and subsequently snapToInterval prop) are set to floats, rather than integers, there seems to be a proportionality increasing offset from the correct interval to snap to. The affect of this is very noticeable with a lot of elements. The further I scroll down a list the bigger the offset, so that when I get to the bottom, it’s very noticeable.

This offset is still there with integers, but it’s a lot less pronounced. Screenshots:

First two are with height set to 105.14285714285714, which is an iPhone 6s Plus’ height divided by seven. The last two have height set to 105, which is just the truncated version of the previous height.

Height: 105.14285714285714 (at bottom): 105 14285714285714 at bottom

Height: 105.14285714285714 (second from top): 105 14285714285714 second from top

Height: 105 (at bottom): 105 at bottom

Height: 105 (second from top): 105 second from top

Reproducible Demo

https://snack.expo.io/@elliotschep/flatlist-snaptointerval-is-not-precise-in-long-list

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 6
  • Comments: 28 (3 by maintainers)

Most upvoted comments

To anyone else facing this: I got around this issue by using snapToOffsets to achieve the same effect.

snapToOffsets solved it for me too.

I just calculated the stops of the next snap by the number of items and the margin between them.

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
        />)

There is a similar issue on Android and horizontal ScrollView with devices like the Nexus 5X which apparently has a screen width of 411.428571429dp (wtf?), both with pagingEnabled and with integer intervals you can see the rounding errors creeping up as you scroll further along the view. On devices with integer screen width there is no problem.

I found in native code that the type of snapToInterval is int (/node_modules/react-native/React/Views/ScrollView/RCTScrollView.h). Therefore, every Float value from JS will be cast as int. I modified type of snapToInterval to Float64 and it worked like a charm

To anyone else facing this: I got around this issue by using snapToOffsets to achieve the same effect.

I’m finding this to only be an issue when setting a custom windowSize={3} to improve performance. When I remove that, the snapToInterval={theme.space[4] + itemWidth} works perfectly. Problem is that windowSize improves performance significantly.

stale bot, this is still an issue as far as I know