I'm trying to create a carousel with infinite looping on finite set of data using FlatList and am using ScrollToIndex to scroll to start when I reach the end and scroll to the end when I reach the start.
However, scrollToIndex causes this weird flashing behaviour when it scrolls from the last banner back to the first, and only on Android (although IOS also has some minor unstable behaviour). Here's the rough code.
const Carousel = () => {
const [activeBanner, setActiveBanner] = useState(0);
const scrollX = useRef(new Animated.Value(0)).current;
const listRef = useRef<RNFlatList>(null);
const goToIndex = (index: number, animated: boolean) => {
listRef.current?.scrollToIndex({
index,
animated,
});
};
const handleScroll = (event: any) => {
const { contentOffset } = event.nativeEvent;
const currentIndex = Math.floor((contentOffset.x - 50) / FULL_BANNER_WIDTH);
if (
currentIndex >= banners.length &&
contentOffset.x >= ACTIVE_BANNER_WIDTH * (banners.length + 1) - 20
) {
goToIndex(1, false);
} else if (currentIndex <= 0 && contentOffset.x <= 0) {
goToIndex(banners.length, false);
}
}
const itemLayout = (_data: any, index: number) => ({
length: FULL_BANNER_WIDTH,
offset: FULL_BANNER_WIDTH * index,
index: index,
});
const onViewableItemsChangedHandler = (info: any) => {
if (info.viewableItems.length > 0) {
setActiveBanner(info.viewableItems[0].index - 1);
} else {
setActiveBanner(0);
}
};
const viewConfigRef = React.useRef([
{
viewabilityConfig: {
waitForInteraction: false,
itemVisiblePercentThreshold: 100,
minimumViewTime: 0,
},
onViewableItemsChanged: onViewableItemsChangedHandler,
},
]);
const flatlistMemo = React.useMemo(() => {
return (
<Animated.FlatList
decelerationRate={0.9}
disableIntervalMomentum={true}
disableScrollViewPanResponder={true}
snapToInterval={FULL_BANNER_WIDTH}
horizontal={true}
showsHorizontalScrollIndicator={false}
ref={listRef}
data={renderItem}
renderItem={renderItems}
keyExtractor={keyExtractor}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: scrollX } } }],
{ useNativeDriver: true, listener: event => handleScroll(event) }
)}
scrollEventThrottle={16}
onScrollToIndexFailed={onScrollFail}
getItemLayout={itemLayout}
onScrollBeginDrag={() => setIsDragging(true)}
onScrollEndDrag={() => setIsDragging(false)}
initialScrollIndex={1}
contentContainerStyle={[
styles.flatListContainer,
{
marginLeft: -HIDDEN_BANNER_PORTION,
},
]}
viewabilityConfigCallbackPairs={viewConfigRef.current}
/>
);
}, [renderItem]);
return (
<View>{flatlistMemo}</View>
)
}
Things I have tried:
removing use of setState to track active banner
using/removing flatlist memoization
key extractor
removed animations
configured windowsize, etc
changed to ScrollView / ScrollTo