import React, { useRef, useState, useEffect, MutableRefObject } from 'react'; import { Text, View, StyleSheet, FlatList, ListRenderItem, Animated, NativeScrollEvent, NativeSyntheticEvent, Pressable, LayoutRectangle, TextStyle, } from 'react-native'; export interface Props { cards: Array; rtlFlag?: boolean; } interface ICard { id: string; title: string; subtitle: string; child: JSX.Element; titleStyle?: TextStyle; subtitleStyle?: TextStyle; } const Carousel: React.FC = ({ cards, rtlFlag = false }) => { const offsetX = useRef(new Animated.Value(0)).current; const flatListRef: MutableRefObject = useRef(); const flatListChildRef: MutableRefObject = useRef(); const flatListSnapToIndex = useRef({ value: 0 }).current; const [viewLayout, setViewLayout] = useState(null); const viewWidth = viewLayout ? viewLayout.width : null; const itemSize = viewLayout ? viewLayout.width / 3 : null; const revertIndex = (index: number) => { return cards.length - 1 - index; }; const onScrollAnim = Animated.event([{ nativeEvent: { contentOffset: { x: offsetX } } }], { useNativeDriver: false }); const renderItem: ListRenderItem = ({ item, index }) => itemSize ? ( scrollToIndex(rtlFlag ? revertIndex(index) : index)} itemSize={itemSize} titleStyle={item.titleStyle} subtitleStyle={item.subtitleStyle} /> ) : null; const scrollToIndex = (index: number) => { flatListRef?.current?.scrollToIndex({ index: index, animated: true }); flatListChildRef?.current?.scrollToIndex({ index: index, animated: true }); }; const renderItemChild: ListRenderItem = ({ item, index }) => viewWidth ? : null; const onScrollHandler = (event: NativeSyntheticEvent, isMainList: boolean) => { isMainList && onScrollAnim(event); const scrollOffsetX = event.nativeEvent.contentOffset.x; const divider = itemSize && viewWidth ? (isMainList ? itemSize : viewWidth) : 1; flatListSnapToIndex.value = Math.round(scrollOffsetX / divider); }; const onScrollEndDragHandler = (event: NativeSyntheticEvent) => { const allowedIndex = flatListSnapToIndex.value < 0 ? 0 : flatListSnapToIndex.value > cards.length - 1 ? cards.length - 1 : flatListSnapToIndex.value; scrollToIndex(allowedIndex); }; return ( setViewLayout(event.nativeEvent.layout)} style={styles.container}> {viewLayout && itemSize && viewWidth ? ( onScrollHandler(event, true)} scrollEventThrottle={1} onScrollEndDrag={event => onScrollEndDragHandler(event)} renderItem={renderItem} data={cards} keyExtractor={(item, index) => item.id} horizontal getItemLayout={(data, index) => ({ length: itemSize, offset: itemSize * index, index })} showsHorizontalScrollIndicator={false} initialScrollIndex={Math.floor(cards.length / 2) - 1} ListHeaderComponent={() => } ListFooterComponent={() => } /> onScrollHandler(event, false)} scrollEventThrottle={1} onScrollEndDrag={event => onScrollEndDragHandler(event)} renderItem={renderItemChild} data={cards} keyExtractor={(item, index) => item.id} horizontal getItemLayout={(data, index) => ({ length: viewWidth, offset: viewWidth * index, index })} showsHorizontalScrollIndicator={false} initialScrollIndex={Math.floor(cards.length / 2) - 1} /> ) : null} ); }; interface IItemProps { offsexX: Animated.Value; index: number; title: string; subtitle: string; center: () => void; itemSize: number; titleStyle?: TextStyle; subtitleStyle?: TextStyle; } const Item = ({ offsexX, index, title, subtitle, center, itemSize, titleStyle, subtitleStyle }: IItemProps) => { const centerPoint = itemSize * index; const opacity = offsexX.interpolate({ inputRange: [centerPoint - itemSize, centerPoint, centerPoint + itemSize], outputRange: [0.4, 1, 0.4], }); const translateY = offsexX.interpolate({ inputRange: [centerPoint - itemSize, centerPoint, centerPoint + itemSize], outputRange: [-20, 1, -20], }); const scale = offsexX.interpolate({ inputRange: [centerPoint - itemSize, centerPoint, centerPoint + itemSize], outputRange: [0.6, 1, 0.6], }); return ( {title} {subtitle} ); }; const itemStyles = StyleSheet.create({ container: { // width: ITEM_SIZE, // height: ITEM_SIZE, alignItems: 'center', justifyContent: 'center', }, pressable: { alignItems: 'center', justifyContent: 'center', }, title: { fontSize: 26 }, subtitle: { fontSize: 18 }, }); interface IViewItem { child: JSX.Element; viewWidth: number; } const ViewItem = ({ child, viewWidth }: IViewItem) => { return ( {child ?? ( Empty )} ); }; const styles = StyleSheet.create({ container: { flex: 1 }, }); export default Carousel;