DateTimeScroll / React Native UI 만들기

DateTimeScroll / React Native UI 만들기
DateTimeScroll을 만들기 위해서는 시와 분을 나타내기 위한 두 개의 TimeScroll 컴포넌트와 TimeScroll 인터렉션을 관리하기 위한 useTimeScroll 훅이 필요하다.
아래의 그림과 같이 가운데에 오는 숫자를 인식해서 해당 값을 저장하는 TimeScroll을 만들고, 이 TimeScroll 두개를 나열하여 시/분을 관리할 수 있도록 DateTimeScroll을 만들어 보자.

DateTimeScroll, ScrollView를 이용한 TimeScroll 만들기
Hook: useTimeScroll.ts
import {useCallback, useEffect} from 'react';
import Animated, {
runOnJS,
scrollTo,
useAnimatedRef,
useAnimatedScrollHandler,
useSharedValue,
} from 'react-native-reanimated';
type Props = {
height: number;
currentData?: number;
dataList?: number[];
onChange: (value: number) => void;
};
const useTimeScroll = ({
height,
currentData,
dataList = [],
onChange,
}: Props) => {
const scrollPageEnabled = useSharedValue(true);
const scrollY = useSharedValue(0);
const scrollRef = useAnimatedRef<Animated.ScrollView>();
const scrollHandler = useAnimatedScrollHandler({
onBeginDrag: event => {
scrollPageEnabled.value = true;
},
onScroll: event => {
scrollY.value = event.contentOffset.y;
},
onEndDrag: event => {},
onMomentumEnd: event => {
console.log('onMomentumEnd', scrollY.value);
if (scrollPageEnabled.value) {
const index = Math.round(scrollY.value / height);
runOnJS(onChange)(dataList[index]);
const dy = height * index;
scrollTo(scrollRef, 0, dy, true);
}
scrollPageEnabled.value = false;
},
});
const change = useCallback(
(index: number) => {
onChange(dataList[index]);
const dy = height * index;
scrollRef.current?.scrollTo({x: 0, y: dy, animated: true});
},
[dataList, height, onChange, scrollRef],
);
useEffect(() => {
if (currentData) {
change(dataList.findIndex(data => data === currentData));
}
}, [change, currentData, dataList]);
return {scrollRef, scrollHandler, change};
};
export default useTimeScroll;
Component: TimeScroll.tsx
const height = 80;
type TimeProps = {
selected: boolean;
value: number;
onPress: () => void;
};
const Time = ({selected = false, value, onPress}: TimeProps) => {
return (
<TouchableOpacity style={styles.time} onPress={onPress}>
<Text style={[styles.time__text, selected && styles.time__textSelected]}>
{value.toString().padStart(2, '0')}
</Text>
</TouchableOpacity>
);
};
type TimeScrollProps = {
currentData: number;
dataList?: number[];
onChange: (data: number) => void;
};
const TimeScroll = ({
currentData,
dataList = [],
onChange,
}: TimeScrollProps) => {
const {scrollRef, scrollHandler, change} = useTimeScroll({
height,
currentData,
dataList,
onChange,
});
return (
<View style={styles.timeScroll}>
<Animated.ScrollView
showsVerticalScrollIndicator={false}
ref={scrollRef}
onScroll={scrollHandler}
onMomentumScrollEnd={event => {}}>
<TouchableOpacity style={styles.timeScroll__offset} />
{dataList.map((data, dataIndex) => {
return (
<Time
key={dataIndex}
value={data}
onPress={() => change(data)}
selected={currentData === data}
/>
);
})}
<TouchableOpacity style={styles.timeScroll__offset} />
</Animated.ScrollView>
</View>
);
};
DateTimeScroll, 두개의 타임 스크롤을 이용하여 시간과 분을 나타내기
const DateTimeScroll = ({
time = moment().valueOf(),
onChange,
}: DateTimeScrollProps) => {
const now = moment(time);
const hourList = Array.from({length: 24}, (_, i) => i);
const minuteList = Array.from({length: 60}, (_, i) => i);
return (
<View style={styles.dateTimeScroll}>
<TimeScroll
currentData={now.hours()}
dataList={hourList}
onChange={hour => {
onChange(moment(time).hour(hour).valueOf());
}}
/>
<View style={styles.divider}>
<Text style={styles.divider__text}>:</Text>
</View>
<TimeScroll
currentData={now.minutes()}
dataList={minuteList}
onChange={minute => {
console.log('minute', minute);
onChange(moment(time).minute(minute).valueOf());
}}
/>
</View>
);
};
DateTimeScroll, Screen 컴포넌트에서 사용하는 방법
import React, {useState} from 'react';
import moment from 'moment';
const Screen = () => {
const [time, setTime] = useState(
moment().hour(7).minute(30).valueOf(),
);
return <DateTimeScroll time={time} onChange={setTime} />
}
export default Screen;