인터랙션과 레이아웃
레이아웃 트랜지션
layout-* 토큰으로 크기와 위치 변화를 움직이고, 자식에게 물려주는 방식을 정해요.
내용이 나타나거나, 글자가 줄바꿈되거나, 옆 요소가 사라지면서 컴포넌트의 크기나 위치가 바뀔 때가 있어요. 이때 layout-* 토큰을 쓰면 새 레이아웃까지 UI 스레드에서 부드럽게 움직여요.
1<Strx.View animate="layout-spring">2 <Strx.Text>제목</Strx.Text>3 {open && <Strx.Text>펼쳐진 내용</Strx.Text>}4</Strx.View>레이아웃 토큰
| 토큰 | 동작 | 모션 |
|---|---|---|
layout-linear | 타이밍으로 움직여요. | 300ms, 또는 duration-* / Provider 지속 시간 |
layout-spring | 스프링으로 움직여요. | damping 15, stiffness 120, mass 1 |
layout-fade | 타이밍으로 움직이면서 뷰가 서서히 나타나요. | 300ms, 또는 duration-* / Provider 지속 시간 |
layout-spring-stiff | 더 단단한 스프링으로 움직여요. | damping 20, stiffness 180, mass 0.9 |
layout-spring-bouncy | 더 통통 튀는 스프링으로 움직여요. | damping 10, stiffness 105, mass 1 |
레이아웃 토큰은 Reanimated의 layout prop으로 전달돼요. linear와 fade 트랜지션은 같은 그룹의 duration-*를 먼저 쓰고, 없으면 Strx.Provider의 motionPreset.duration, 그것도 없으면 300ms를 써요. 스프링 트랜지션은 각자 정해진 값으로 움직여요.
<Strx.View animate="layout-linear duration-180" />레이아웃 토큰은 같은 그룹 안에서 등장, 트랜지션, 퇴장 토큰과 자유롭게 함께 쓸 수 있어요.
<Strx.View animate="fade-in from:translate-y-12 layout-spring exit:opacity-0" />물려받기
STRX 컴포넌트는 이럴 때 레이아웃 변화를 움직여요.
- 자기에게
layout-*토큰이 있을 때 - 물려받은 트랜지션이 있을 때. 레이아웃 트랜지션이 있는 가장 가까운 STRX 부모나, 가장 가까운
Strx.LayoutGroup에서 물려받아요.
그래서 컨테이너에 토큰 하나만 달아도 안에 있는 STRX 컴포넌트가 모두 함께 움직여요.
1<Strx.View animate="layout-spring">2 <Strx.View>{/* layout-spring을 물려받아요 */}</Strx.View>3 <Strx.Text>{/* layout-spring을 물려받아요 */}</Strx.Text>4</Strx.View>물려받기는 아래로만 흘러요. 자식에게 layout-* 토큰이 있다고 부모까지 움직이지는 않아요. 안에 내용이 나타날 때 컨테이너 크기도 부드럽게 바뀌어야 한다면, 컨테이너에 토큰을 달거나 그 영역을 Strx.LayoutGroup으로 감싸 주세요.
1// 카드 크기는 부드럽게 바뀌고, 새 텍스트는 서서히 나타나요2<Strx.View animate="layout-spring">3 {open && <Strx.Text animate="fade-in">상세 내용</Strx.Text>}4</Strx.View>물려받기는 React context를 통해 STRX 컴포넌트에게 전달돼요. 중간에 일반 React Native View가 끼어 있어도 연결은 끊기지 않지만, 그 View 자체는 움직이지 않아요.
물려받지 않게 하기
layoutPropagation="none"을 쓰면 그 컴포넌트와 자식들은 물려받은 트랜지션을 쓰지 않아요. 자기 layout-* 토큰이 있다면 그것만 쓰고, 자식들도 그 토큰을 물려받아요.
1<Strx.View animate="layout-spring">2 <Strx.View layoutPropagation="none">3 <Strx.Text>여기서는 레이아웃이 바뀌어도 움직이지 않아요.</Strx.Text>4 </Strx.View>5</Strx.View>실시간 카운터나 입력창처럼 크기가 자주 바뀌어서 애니메이션이 오히려 답답하게 느껴지는 곳, 또는 움직이는 컨테이너 안에 있는 독립된 위젯에 써 보세요.
넘치는 내용 자르기
뷰가 작아지는 동안에는 안의 내용이 밖으로 삐져나올 수 있어요. layoutClip을 쓰면 레이아웃 트랜지션이 적용되는 동안(직접 단 것이든 물려받은 것이든) overflow: "hidden"을 넣어 줘요.
<Strx.View animate="layout-spring" layoutClip>
{open && <LongContent />}
</Strx.View>글자 그림자나 배지가 예상치 못하게 잘리지 않도록, 필요할 때만 켜게 되어 있어요.
성능 부담
레이아웃 트랜지션이 없는 컴포넌트에는 Reanimated layout prop을 아예 넘기지 않아요. 그래서 큰 화면에 STRX를 써도 레이아웃 비용이 들지 않아요. 한 번 레이아웃 트랜지션이 적용된 컴포넌트는 prop을 없애는 대신 아무것도 하지 않는 트랜지션을 유지해서, 네이티브 뷰가 불필요하게 다시 만들어지지 않게 해요.
긴 리스트
스크롤이 긴 리스트라면 모든 행에 layout-* 토큰을 다는 것보다 Reanimated의 Animated.FlatList와 itemLayoutAnimation을 쓰는 게 좋아요. 각 행의 등장·퇴장은 여전히 STRX로 처리할 수 있어요.
1import Animated, { LinearTransition } from "react-native-reanimated";23<Animated.FlatList4 data={items}5 keyExtractor={item => item.id}6 itemLayoutAnimation={LinearTransition}7 renderItem={({ item }) => (8 <Strx.View animate="fade-in exit:opacity-0">9 <Row item={item} />10 </Strx.View>11 )}12/>;동작 줄이기
동작 줄이기가 켜져 있으면 레이아웃 변화가 바로 적용돼요. 자세한 내용은 접근성에서 확인해 보세요.
layout-*와 transition-layout은 어떻게 다른가요?
layout-* | transition-layout | |
|---|---|---|
언제 움직이나요 | 내용, 옆 요소, flexbox 등으로 실제 레이아웃이 바뀔 때 | style의 width/height/위치 값을 바꿀 때 |
어디까지 움직이나요 | 실제로 잰 레이아웃까지 | 직접 정한 값까지 |
자식에게 물려주나요 | 네, STRX 자식 컴포넌트에게 | 아니요 |