Skip to content

Instantly share code, notes, and snippets.

@cjmling
Last active August 23, 2024 10:06
Show Gist options
  • Save cjmling/84e271ea61d89cd7ef429a4b95f57b9e to your computer and use it in GitHub Desktop.
Save cjmling/84e271ea61d89cd7ef429a4b95f57b9e to your computer and use it in GitHub Desktop.
react-native animated vertical select
import React, { useRef, useState } from 'react';
import { View, StyleSheet, ScrollView } from 'react-native';
import Animated, {
useSharedValue,
interpolate,
useAnimatedStyle,
Extrapolation,
} from 'react-native-reanimated';
// Changeable
const FONT_SIZE = 20;
const SCROLL_HEIGHT = 400;
const ITEM_BACKGROUND = "#FFF";
const ITEM_WIDTH = 100;
// Should not change
// 5 Because we want to only show 5 item in the scroll list. Should not change this value as it is hard coded and used in interpolate function. But one day if we want to change then it should be odd number.
const ITEM_HEIGHT = SCROLL_HEIGHT / 5; //
const TOTAL_ITEM_PER_SCROLL_VIEW = Math.round(SCROLL_HEIGHT / ITEM_HEIGHT);
const MIDDLE_ITEM_OF_SCROLL_VIEW = Math.round(TOTAL_ITEM_PER_SCROLL_VIEW / 2);
export const VerticalSelectScrollView = ({onChange, datas}: {onChange:(value: any) => void, datas: {id: string, title: string}[]}) => {
const contentOffset = useSharedValue(0);
const firstFocusedItemInScroll = useSharedValue(0);
const scrollViewRef = useRef<any>(null);
// Add 2 items in the start and end because they aren't selectable in scroll
const EXTRA_OFFSET_ITEMS_PER_SIDE = 2;
const EXTENDED_ITEMS = [...[{id: '-2', title: ''}, {id: '-1', title: ''}],...datas, ...[{id: '-3', title: ''}, {id: '-4', title: ''}]]
const Item = ({
item,
index,
}: {
item: { id: string; title: string };
index: number;
}) => {
const viewStyle = useAnimatedStyle(() => {
const inputRange = [index - 2, index - 1, index, index + 1, index + 2];
let outputRange = [0.2, 0.5, 1, 0.5, 0.2];
const activeItem =
firstFocusedItemInScroll.value + MIDDLE_ITEM_OF_SCROLL_VIEW - 1; // ActiveItem are the middleone in visible view. -1 Because MIDDLE_ITEM_OF_SCROLL_VIEW value get rounded up above.
const opacity = interpolate(
activeItem,
inputRange,
outputRange,
Extrapolation.CLAMP
);
return {
opacity,
};
});
const fontStyle = useAnimatedStyle(() => {
const inputRange = [index - 2, index - 1, index, index + 1, index + 2];
const activeItem =
firstFocusedItemInScroll.value + MIDDLE_ITEM_OF_SCROLL_VIEW - 1; // ActiveItem are the middleone in visible view
const fontSize = interpolate(
activeItem,
inputRange,
[
0.8 * FONT_SIZE,
0.9 * FONT_SIZE,
1 * FONT_SIZE,
0.9 * FONT_SIZE,
0.8 * FONT_SIZE,
],
Extrapolation.CLAMP
);
return {
fontSize,
};
});
return (
<Animated.View
style={[
{
backgroundColor: ITEM_BACKGROUND,
height: ITEM_HEIGHT,
width: ITEM_WIDTH,
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
viewStyle,
]}>
<Animated.Text style={[{ fontSize: 20 }, fontStyle]}>
{item.title}
</Animated.Text>
</Animated.View>
);
};
const onMomentumScrollEnd = () => {
const distanceFromTop = contentOffset.value;
const itemFromTop = Math.round(distanceFromTop / ITEM_HEIGHT);
onChange(EXTENDED_ITEMS[itemFromTop + EXTRA_OFFSET_ITEMS_PER_SIDE]);
};
return (
<View style={styles.container}>
<ScrollView
onScroll={(event) => {
contentOffset.value = event.nativeEvent.contentOffset.y;
const scrollDistanceFromTop = Math.abs(contentOffset.value);
// Item distance from top
firstFocusedItemInScroll.value = scrollDistanceFromTop / ITEM_HEIGHT; //Should not round number
}}
decelerationRate={'normal'}
snapToOffsets={EXTENDED_ITEMS.map((ITEM, index) => ITEM_HEIGHT * index)}
ref={scrollViewRef}
onMomentumScrollEnd={onMomentumScrollEnd}
showsVerticalScrollIndicator={false}
snapToAlignment="center">
{EXTENDED_ITEMS.map((ITEM, index) => (
<Item item={ITEM} index={index} />
))}
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
height: SCROLL_HEIGHT,
backgroundColor: '#EEE',
},
});
@cjmling
Copy link
Author

cjmling commented Aug 23, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment