Last active
May 17, 2023 13:03
-
-
Save dungkaka/ca03ccc3ddbc2ebf6660ffec0c54c1e2 to your computer and use it in GitHub Desktop.
Wheel picker using Flatlist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi apparently doesnt work anymore. Any chance to revisit it?