UNPKG

@uiw/react-native

Version:
212 lines 6.62 kB
import React, { useState, useEffect, useRef, useMemo } from 'react'; import { View, StyleSheet, Text, ScrollView, Animated, Platform, Pressable } from 'react-native'; const Picker = props => { const { lines = 3, rowKey = 'label', data = new Array(), containerStyle = {}, textStyle = {}, value = 0, onChange, readonly = false, onScrollEnd } = props; const Y = useRef(new Animated.Value(0)).current; const scrollView = useRef(); const ItemHeights = useRef([]).current; const saveY = useRef(0); const timer = useRef(); const onPressTimer = useRef(); const onPressORonScroll = useRef('onScroll'); const currentY = useRef(0); const [current, setCurrent] = useState(0); useEffect(() => { onChange?.(current); onPressORonScroll.current = 'onScroll'; clearTimeout(timer.current); timer.current = undefined; clearTimeout(onPressTimer.current); }, [current]); useEffect(() => { if (value !== current) { let leng = value > data.length - 1 ? data.length - 1 : value; leng = leng < 0 ? 0 : leng; location(style.containerHeight * (leng + 1), leng); } }, [value]); const style = useMemo(() => { const containerUn = StyleSheet.flatten([styles.container, containerStyle.unactived]); const containerAc = StyleSheet.flatten([styles.container, styles.border, containerStyle.actived]); const textUn = StyleSheet.flatten([styles.textStyle, textStyle.unactived]); const textAc = StyleSheet.flatten([styles.textStyle, styles.acTextStyle, textStyle.unactived, textStyle.actived]); const containerHeight = containerUn.height || 50; return { containerAc, containerUn, textUn, textAc, containerHeight }; }, [containerStyle, textStyle]); const getItemHeight = event => { const { height } = event.nativeEvent.layout; const round = Math.round(height); ItemHeights.push(round * ItemHeights.length + round); }; const location = (scrollY, index) => { saveY.current = scrollY - style.containerHeight; currentY.current = index; let an = Platform.OS === 'android' && { duration: 0 }; let os = Platform.OS === 'ios' && { animated: false }; scrollView.current?.scrollTo({ x: 0, y: scrollY - style.containerHeight, ...an, ...os }); }; const setScrollHandle = val => { const spot = val / ItemHeights[0]; if (spot <= 0.6) { scrollView.current?.scrollTo({ x: 0, y: 0, animated: true }); setCurrent(0); return false; } const stringSpot = spot + '.0'; const integer = Math.floor(spot); const decimal = Number(stringSpot[stringSpot.indexOf('.') + 1]); const itemIndex = decimal >= 6 ? integer + 1 : integer; scrollView.current?.scrollTo({ x: 0, y: ItemHeights[itemIndex] - ItemHeights[0], animated: true }); saveY.current = ItemHeights[itemIndex] - ItemHeights[0]; setCurrent(itemIndex); clearTimeout(timer.current); timer.current = undefined; }; const listener = event => { if (onPressORonScroll.current === 'onPress') { clearTimeout(onPressTimer.current); onPressTimer.current = setTimeout(() => { setCurrent(currentY.current); clearTimeout(onPressTimer.current); }, 16); return; } saveY.current = event.nativeEvent.contentOffset.y; if (timer.current) { clearTimeout(timer.current); timer.current = undefined; } timer.current = setTimeout(() => { setScrollHandle(saveY.current); }, 160); }; const onTouchEnd = () => { if (readonly) return; if (Platform.OS === 'ios') { if (onPressORonScroll.current === 'onPress') { setCurrent(currentY.current); return; } if (timer.current) return; setScrollHandle(saveY.current); } }; const getBlankHeight = useMemo(() => { if (lines % 2) { return { top: Math.floor(lines / 2), center: Number((lines / 2).toFixed()), bottom: Math.floor(lines / 2) }; } return { top: lines / 2 - 1, center: lines / 2 + 1, bottom: lines / 2 }; }, [lines]); const onMomentumScrollEnd = () => { onScrollEnd?.(); }; return <View style={{ paddingVertical: 0, height: style.containerHeight * lines }}> <ScrollView showsVerticalScrollIndicator={false} ref={scrollView} scrollEventThrottle={16} onTouchEnd={Platform.OS === 'ios' ? onTouchEnd : undefined} onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: Y } } }], { listener, useNativeDriver: false })} scrollEnabled={!readonly} onMomentumScrollEnd={onMomentumScrollEnd}> {<Pressable style={[style.containerUn, { height: style.containerHeight * getBlankHeight.top }]} onPressOut={Platform.OS === 'android' ? onTouchEnd : undefined} />} {data.map((item, index) => <Pressable onLayout={getItemHeight} key={index} onPressOut={Platform.OS === 'android' ? onTouchEnd : undefined} onPress={() => { if (readonly) return; // if (timer.current) return; clearTimeout(onPressTimer.current); onPressORonScroll.current = 'onPress'; location(ItemHeights[index], index); }}> {React.isValidElement(item.render?.(item[rowKey], item, index)) ? item.render?.(item[rowKey], item, index) : <View style={style.containerUn}> <Text style={current === index ? style.textAc : style.textUn}>{item[rowKey]}</Text> </View>} </Pressable>)} {<Pressable style={[style.containerUn, { height: style.containerHeight * getBlankHeight.bottom }]} onPressOut={Platform.OS === 'android' ? onTouchEnd : undefined} />} </ScrollView> <View style={[style.containerAc, { top: -style.containerHeight * getBlankHeight.center }]} /> <View style={[style.containerAc, { top: -style.containerHeight * (getBlankHeight.center - 1) }]} /> </View>; }; const styles = StyleSheet.create({ container: { height: 50, justifyContent: 'center', alignItems: 'center', paddingVertical: 0, paddingHorizontal: 0 }, border: { backgroundColor: '#E6E6E6', height: 1, position: 'relative', zIndex: 999, paddingVertical: 0, paddingHorizontal: 0 }, textStyle: { fontSize: 20, color: '#000', paddingVertical: 0, paddingHorizontal: 0 }, acTextStyle: { color: '#1677ff' } }); export default Picker;