[React-Native] Scrollable Time Picker 만들기 [2]

HYUNGU, KANG·2022년 6월 5일
7

Dive into React-Native

목록 보기
2/2

Snap(포커싱) 동작 구현하기

스크롤이 멈추는 시점의 offset.y 를 이용하여, 버튼의 정확한 위치를 구하고
scrollTo 를 이용해 포커싱 동작을 구현합니다.

Snap(포커싱) 동작 구현

code example

스크롤이 멈추는 순간 우리는 버튼의 위치를 알아낸 뒤, 현재 위치의 좌표를 이동시킬 정확한 좌표로 변환하고
해당 위치로 scrollTo 를 사용해서 이동시킬 수 있습니다.

우리는 버튼의 높이를 고정값으로 주었기 때문에, 정확한 좌표로 변환 하는것은 어렵지 않습니다.
시간 스크롤뷰를 기준으로 설명을 간단하게 해보겠습니다.


스크롤뷰의 초기 offset.y0 입니다.


스크롤뷰를 버튼 높이의 1/3 만큼 이동하게 되면, offset.yBUTTON_HEIGHT * 0.3 이 됩니다.
즉, 스크롤을 이동하는 만큼 offset.y 가 증가하게 됩니다.

이제 여기서 우리가 원하는 요구사항을 조금 더 구체적으로 생각해보겠습니다.
우리는 가운데 포커스된 영역에 버튼이 정확하게 위치하기를 원하고 있습니다.

우리가 원하는 가운데 포커스된 영역만을 살펴보면, 01 보다는 12 가 더 많이 차지하고 있습니다.
이럴 경우에 12 버튼이 가운데 위치하게 되도록 하려면 어떻게 하면 될까요?

단순합니다. 12 버튼이 정확하게 가운데 위치하는 offset.y 를 구해서 scorllRef.scrollTo({y: offsetY}) 를 호출하면 됩니다.
12 버튼이 가운데 오려면 가장 초기의 상태, 즉 offset.y0 일 때입니다.

그렇다면 01 버튼이 가운데 오려면 offset.y 는? BUTTON_HEIGHT 만큼 증가하면 됩니다.
마찬가지로 02 버튼이 가운데 오려면 BUTTON_HEIGHT 만큼 증가한, 2 * BUTTON_HEIGHT 가 되면 됩니다.
이런식으로 특정 버튼을 가운데 오게 하려면, offset.yindex * BUTTON_HEIGHT 에 위치하게 하면 된다는 규칙을 찾을 수 있습니다.

스크롤이 멈춘 시점의 offset.y 를 이용한다면 간단하게 반올림을 이용해서, 현재 위치의 index 를 구해서 버튼의 정확한 offset.y 좌표를 계산할 수 있습니다.

const getCenterPosition = (offsetY) => {
  const buttonIndex = Math.round(offsetY / BUTTON_HEIGHT);
  // 예시 코드이므로, 공백 영역은 알아서 계산..
  console.log('selected item', items[buttonIndex]); 
  return buttonIndex * BUTTON_HEIGHT;
};

<ScrollView
  ref={scrollRef}
  onScrollEndDrag={event => {
    const correctOffset = getCenterPosition(event.nativeEvent.contentOffset.y);
    scorllRef.scrollTo({y: correctOffset});
  }}
  ...
>
  {items.map( ... )}
</ScrollView>

포커싱 애니메이션 구현하기

interpolate 를 이용하여 스크롤의 위치에 따른 포커싱 애니메이션을 구현합니다.

포커싱 애니메이션 구현

code example

위의 과정에서 버튼의 index 를 통해서 offset.y 를 구하는 규칙을 알아냈습니다.
이를 이용하면 interpolate 를 이용하여 손쉽게 가운데 위치한 버튼에만 하이라이팅과 같은 애니메이션을 줄 수 있습니다.

const animatedValue = useRef(new Animated.Value(0)).current;

<ScrollView
  onScroll={Animated.event(
       [{ nativeEvent: { contentOffset: { y: animatedValue } } }],
       { useNativeDriver: false }
  )}
/>

우선 Animated.event 를 사용하여, ScrollView 의 현재 스크롤 위치인 offset.yAnimated.Value 에 매핑시켜 줍니다.
(onScroll 이벤트가 발생될때마다 자동적으로 nativeEvent.contentOffset.y 값이 animatedValue 에 set 됩니다.)

Interpolate 란?

interpolate 는 단순하게 말해서, 증가하는 애니메이션의 값에 대해서 input/output 범위를 대칭되게 설정해서 값을 받아올 수 있습니다.
아래 코드에서 animatedValue 의 값이 inputRange 로 변화할때, 매칭되는 outputRange 에 지정된 값을 반환합니다.
(단순 계산을 하면, 0 일때는 0, 25 일때는 0.15, 50 일때는 0.3, 75 일때는 0.65, 100 일때는 100 입니다.)

const opacity = animatedValue.interpolate({
  inputRange: [0, 50, 100], // animatedValue 의 inputRange 범위에 대하여
  outputRange: [0, 0.3, 1], // 각 범위에 대칭되는 지점에 outputRange 값을 리턴,
  extrapolate: 'clamp', // 지정된 범위에 대해서만 처리 (벗어나는 input 값은 outputRange 최대/최소값으로 처리됨)
})
<AnimatedButton style={{ opacity }}} />

이를 이용해서 각 버튼별로 index 를 통해서 중앙에 포커싱 되는 offset.y 좌표를 구한 뒤 가운데 기준점으로 삼고

  • 위쪽으로 버튼의 높이만큼 범위를 벗어나면 0.3
  • 정확히 가운데에 위치하면 1
  • 아래쪽으로 버튼의 높이만큼 범위를 벗어나면 0.3

의 기준으로 interpolate 의 input/output 범위를 설정합니다.

const buttonItemIndex = N;
const correctOffset = buttonItemIndex * BUTTON_HEIGHT;
const opacity = animatedValue.interpolate({
  inputRange: [correctOffset - BUTTON_HEIGHT, correctOffset, correctOffset + BUTTON_HEIGHT], 
  outputRange: [0.3, 1, 0.3], 
  extrapolate: 'clamp', 
})

이를 활용하면 opacity 이외에도 다양한 포커싱 애니메이션을 줄 수 있습니다.


전체 코드는 아래의 레포에서 확인하실 수 있습니다.
https://github.com/bang9/react-native-wheel-time-picker-example

profile
JavaScript, TypeScript and React-Native

0개의 댓글