Skip to content

Instantly share code, notes, and snippets.

@dungkaka
Last active May 17, 2023 13:03
Show Gist options
  • Save dungkaka/ca03ccc3ddbc2ebf6660ffec0c54c1e2 to your computer and use it in GitHub Desktop.
Save dungkaka/ca03ccc3ddbc2ebf6660ffec0c54c1e2 to your computer and use it in GitHub Desktop.
Wheel picker using Flatlist
import React, { Fragment } from "react";
import { FlatList, StyleSheet, Text, View, Dimensions } from "react-native";
import Animated, {
Extrapolate,
interpolate,
useAnimatedScrollHandler,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
} from "react-native-reanimated";
import MaskedView from "@react-native-masked-view/masked-view";
const { width: WIDTH } = Dimensions.get("window");
const FlatListAnimted = Animated.createAnimatedComponent(FlatList);
const start = 1900;
const values = new Array(new Date().getFullYear() - start + 1)
.fill(0)
.map((_, i) => {
const value = start + i;
return { value, label: `${value}` };
})
.reverse();
const dates = new Array(31).fill(0).map((_, i) => ({ value: i + 1, label: `${i + 1}` }));
const months = new Array(12).fill(0).map((_, i) => ({ value: i + 1, label: `${i + 1}` }));
const Item = React.memo(
({ item, i, scrollPosition, h, half, renderChild }) => {
const animatedItem = useDerivedValue(() => {
const v = (scrollPosition.value - i) / (half + 1);
if (v <= -1) return -1;
if (v >= 1) return 1;
return v;
// interpolate(scrollPosition.value, [i - half - 1, i, i + half + 1], [-1, 0, 1], Extrapolate.CLAMP)
});
const childViewStyle = useAnimatedStyle(() => ({
transform: [
{ perspective: half * 100 },
{ rotateX: 90 * animatedItem.value + "deg" },
{
scale: 1 - 0.1 * Math.abs(animatedItem.value),
},
],
}));
return (
<Animated.View style={[styles.item, { height: h }, childViewStyle]}>
{renderChild({ item, index: i })}
</Animated.View>
);
},
() => true
);
const FlatListPicker = React.memo(
({
// array of data
data = [],
// height of each row in wheel
itemHeight: h = 30,
// it should be odd number (3,5,7,9)
numberVisibleItem: n = 5,
// opacity of inactive item from above and under active item
opacityInactiveItem = 0.5,
// listen when wheel finish scroll to value.
onValueChange = () => {},
// child inside a row of Flatlist
renderChild = ({ item, index }) => <Text style={styles.label}>Item {index}</Text>,
// initial index that wheel display first.
initialIndex = -1,
}) => {
const half = (n - 1) / 2;
const scrollPosition = useSharedValue(0);
const renderItem = ({ item, index }) => {
return <Item item={item} i={index} h={h} scrollPosition={scrollPosition} half={half} renderChild={renderChild} />;
};
const scrollHandler = useAnimatedScrollHandler({
onScroll: (event) => {
scrollPosition.value = event.contentOffset.y / h;
},
onBeginDrag: (e) => {},
onEndDrag: (e) => {},
});
return (
<View style={{ height: n * h }}>
<MaskedView
style={{ flex: 1, flexDirection: "row", height: "100%" }}
maskElement={
<Fragment>
<View style={{ height: h * half, backgroundColor: `rgba(0,0,0,${opacityInactiveItem})` }} />
<View style={{ height: h, backgroundColor: "white" }} />
<View style={{ height: h * half, backgroundColor: `rgba(0,0,0,${opacityInactiveItem})` }} />
</Fragment>
}
>
<FlatListAnimted
windowSize={5}
initialNumToRender={21}
contentContainerStyle={{
paddingTop: h * half,
paddingBottom: h * half,
}}
getItemLayout={(data, index) => ({
length: h,
offset: h * index,
index,
})}
initialScrollIndex={initialIndex}
snapToOffsets={data.map((x, i) => i * h)}
snapToAlignment="center"
keyExtractor={(i) => i.label}
data={data}
renderItem={renderItem}
decelerationRate={0.97}
onScroll={scrollHandler}
removeClippedSubviews={true}
showsVerticalScrollIndicator={false}
fadingEdgeLength={h}
onMomentumScrollEnd={(e) => {
onValueChange(Math.round(e.nativeEvent.contentOffset.y / h));
}}
/>
</MaskedView>
</View>
);
}
);
const Index = () => {
return (
<View style={styles.container}>
<Text style={styles.title}>What year were you born?</Text>
<View style={{ flex: 1 }}>
<View style={{ flex: 1 }}>
<FlatListPicker
data={dates}
initialIndex={3}
renderChild={({ item, index }) => <Text style={styles.label}>{item.label}</Text>}
onValueChange={(index) => console.log("INDEX", index)}
/>
</View>
<View style={{ flex: 1 }}>
<FlatListPicker
data={months}
initialIndex={0}
renderChild={({ item, index }) => <Text style={styles.label}>{item.label}</Text>}
/>
</View>
<View style={{ flex: 1 }}>
<FlatListPicker
data={years}
initialIndex={3}
renderChild={({ item, index }) => <Text style={styles.label}>{item.label}</Text>}
/>
</View>
</View>
);
};
export default Index;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "black",
justifyContent: "center",
alignItems: "center",
},
title: {
color: "white",
fontSize: 24,
marginBottom: 31,
},
item: {
justifyContent: "center",
alignItems: "center",
},
label: {
color: "white",
fontSize: 20,
textAlign: "center",
textAlignVertical: "center",
},
});
@RafaelCENG
Copy link

Hi apparently doesnt work anymore. Any chance to revisit it?

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