Bookmark profile
DateTimeScroll

DateTimeScroll을 만들기 위해서는 시와 분을 나타내기 위한 두 개의 TimeScroll 컴포넌트와 TimeScroll 인터렉션을 관리하기 위한 useTimeScroll 훅이 필요하다.

아래의 그림과 같이 가운데에 오는 숫자를 인식해서 해당 값을 저장하는 TimeScroll을 만들고, 이 TimeScroll 두개를 나열하여 시/분을 관리할 수 있도록 DateTimeScroll을 만들어 보자.

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;