애니메이션 동적 데이터 처리: UI 인터렉션에서의 길이와 순서 변경 관리
애니메이션 동적 데이터 처리: UI 인터렉션에서의 길이와 순서 변경 관리
동적 데이터 처리, 검색엔진에 드래그로 순서를 변경하는 코드를 찾아보면 아주 깔끔하게 잘 나온다. 하지만, 이 코드에는 정말 많은 것들이 빠져있었다.
드래그를 활용하여 순서 변경을 하기 위한 리오더블 리스트 UI를 만들면서 배운점에 대해 간략하게 소개하려고 한다.
애니메이션, 해당 UI가 스크롤뷰 안에 있는 경우
예제 코드로 리오더블 리스트를 구현하면, 화면의 좌측 상단에 딱 붙어있기 때문에, 아무런 이슈가 없었다. 하지만, 위에 제목이 생기고, 디자인을 위해서 리스트 양쪽에 패딩을 넣고 하다보니 뭔가가 잘못된 것을 깨달았다.
갑자기 이동하는 요소의 좌표가 틀어지기 시작했다!
그래서 왜 이런 현상이 발생하는지를 분석하기 시작했고, 결국 알아냈다!
스크롤뷰 안에서 특정 요소가 이동하는 좌표를 가져오는 경우, 절대좌표를 사용한다. 하지만 제공되는 이벤트의 절대좌표 값은 화면 전체를 기준으로 계산된다.
따라서, 스크롤뷰의 시작점을 기준으로 절대좌표가 계산될거라고 예상하고 작성된 코드는 좌표가 틀어질 수 밖에 없는 것이다.
또한, 스크롤뷰가 또다른 스크롤 뷰에 있다면, 감싸고 있는 뷰의 기준점과 스크롤 정도를 모두 알아내서 좌표를 계산하기 위한 기준점을 알아내야 했다.
이 해결책은, 컴포넌트로 전달할 속성에 rootOffset을 추가하는 것이었다.
const rootOffsetX = useSharedValue(0);
const rootOffsetY = useSharedValue(0);
const scrollHandler = useAnimatedScrollHandler({
onMomentumEnd: e => {
rootOffsetX.value = e.contentOffset.x;
rootOffsetY.value = e.contentOffset.y;
},
onEndDrag: e => {
rootOffsetX.value = e.contentOffset.x;
rootOffsetY.value = e.contentOffset.y;
},
});
return (
<Animated.ScrollView onScroll={scrollHandler}>
<ReorderableListView
horizontal
rootOffset={rootOffsetX}
...
/>
<ReorderableListView
rootOffset={rootOffsetY}
...
/>
</Animated.ScrollView>
)
이렇게, 오프셋 값을 추가하면, 더이상 좌표값이 잘못 계산되는 일은 발생하지 않았다.
애니메이션, 이동중인 요소가 스크롤뷰의 양 끝점에 가까워진 경우
실제 사용자 경험을 생각해 보면, 요소를 드래그해서 상/하, 좌/우 끝으로 이동했을 때를 생각해 보자. 나는 자동으로 스크롤이 되었던 경험이 많았던 것 같다.
하지만, 예제 코드에서는 이런 기능이 없다. 그저 이동 후, 스크롤 하고, 다시 또 그 지점부터 이동 해야만 한다. 너무 번거롭다.
그래서 양 끝 점의 기준을 요소의 절반 사이즈로 잡고, 해당 지점에 가까이 왔을 때 스크롤을 하도록 만들었다.
const from =
absoluteOffset -
pageOffset -
(horizontal ? scrollViewLayout.x : scrollViewLayout.y);
const scrollStartLimit = itemSize * 0.5;
if (from < scrollStartLimit) {
runOnJS(onStartReached)();
}
const scrollEndLimit =
(horizontal ? scrollViewLayout.width : scrollViewLayout.height) -
itemSize * 0.5;
if (from > scrollEndLimit) {
runOnJS(onEndReached)();
}
애니메이션, 데이터가 추가되거나, 제거되었을 때
데이터가 변경될 시, 당연히 애니메이션이 자동으로 업데이트 된다고 생각했으나, 그렇지 않았다. State로 관리하는 값들이 아니기 때문이다. 데이터가 변경되더라도 모든 애니메이션 파라미터는 값이 유지되고 있었다.
그래서 별도로 데이터가 변경되면 순서배열을 수정하여 인터렉션이 동작하도록 해야했다.
useEffect(() => {
if (orders.value.length < items.length) {
orders.value = [...orders.value, items.length - 1];
} else {
orders.value = new Array(items.length).fill(0).map((__, order) => order);
}
}, [items, orders]);
가로/세로 방향에 대한 테스트 영상
나는 리오더블 리스트를 벌써 5번째 새로 만든다. 이렇게 만들면서 느낀 점은, 매번 만들면서 매번 배운다. 기존의 코드를 수정하지 않고 그냥 지우고 다시 만들어야만 내가 어떤거를 고려해야하는지를 정확히 알 수 있다.
버그를 수정하는 능력을 기르는 것도 좋지만, 무언가를 만들어 내는 능력을 기르는 것이 더 좋다고 생각한다. 그래야 코드 전체를 읽으면서 내가 어떤 것들이 부족했고, 왜 이 코드를 작성했는지 언어화 할 수 있기 때문이다.