REACT NATIVE
ANIMATION
&
INTERACTION
리액트-네이티브(React-Native)는 페이스북(현 메타)에서 개발된 오픈소스 모바일 어플리케이션 프레임워크입니다.
iOS, Android 그 외 다른 플랫폼에서 React의 방식으로 똑같이 개발할 수 있는 것이 특징인 대표적인 크로스 플랫폼(cross-platform) 중 하나이기도 합니다.
이번 포스팅에서는 리액트-네이티브에서 사용되는 애니메이션 & 상호작용에 관한 정보나 기술들의 동향들을 소개해 보고자 합니다.
"그리고 소개에 도움을 더하기 위해 요즘 아주 핫한 chatGPT 의 의견도 같이 첨부해 보았습니다."
먼저, 애니메이션 & 상호작용이란 무엇일까요?
애니메이션[ Animation ]은 모션 그래픽(motion graphics), 트랜지션(transitions), 그 외 다른 시각적 효과들을 활용하여 유저들에게 더 나은 사용자경험(User Experience)과 앱 커뮤니케이션 제공합니다.
상호작용[ Interaction ]은 사용자가 앱 내부에서 어떤 방식으로 화면을 이동하고, 데이터 입력이나 이벤트를 발생시키고, 피드백을 받는 방식 등을 말합니다. 효율적이고 직관적인 상호작용이 고려된 설계는 앱이 목표한 바를 조금 더 빠르고 쉽게 접근할수 있도록 도와줍니다.
리액트-네이티브에서의 애니메이션 & 상호작용 기술은 어떤 장점이 있을까요?
1. 크로스-플랫폼 지원 : 리액트 네이티브는 하나의 코드로 여러기기와 OS를 지원합니다.
2. High performance : 리액트 네이티브에서 제공하는 Animated API 는 특정 플랫폼이나 기기에 최적화 될 수 있는 고성능의 애니메이션을 제공합니다. 큰 자원 소모 없이 부드러운 애니메이션을 가능케하여, 더 나은 사용자 경험(UX)을 제공합니다.
3. 단순한 개발 : 리액트 네이티브에서 제공하는 Animated API 기능들은 복잡한 애니메이션을 상대적으로 쉬운 코드로도 작성할 수 있게끔 하여 우리의 개발과정에서 시간과 노력을 줄여줍니다.
4. 리액트로 통합 : 리액트 네이티브의 애니메이션은 리액트 기반위에 빌드되어 개발자들에게 친숙한 컨셉으로 다가오고 리액트 환경에서 연습해 볼 수 있습니다.
chatGPT에 의하면 리액트 네이티브 자체에 내장된 애니메이션 기술들이 이미 최적화가 잘 되어 있고, 기술을 익히기가 비교적 쉽다는 점을 장점으로 꼽고 있습니다.
1. Animation
리액트 네이티브는 자체적으로 고성능 애니메이션 API를 제공합니다.
그래서 우리는 외부 라이브러리의 도움 없이 자체적으로 지원하는 기술로도 꽤 괜찮은 애니메이션을 구현할 수 있습니다.
공식 DOCUMENT - API animated ) https://reactnative.dev/docs/animated
Animated · React Native
The Animated library is designed to make animations fluid, powerful, and painless to build and maintain. Animated focuses on declarative relationships between inputs and outputs, configurable transforms in between, and start/stop methods to control time-ba
reactnative.dev
해당 공식 문서는 리액트 네이티브 자체에서 지원하는 animation API 관련 기술들을 제공해줍니다.
문서에서 굵직한 포인트들을 하나씩 훑어 보겠습니다.
- Animated.Value() - 한가지 value 에 애니메이션 동작을 지정할 때 사용되는데, 여기에는 컴포넌트의 x축 또는 y축 이동 애니메이션뿐 아니라 scale, border-radius 등 다양하게 지정하여 구현할 수 있습니다.
- Animated.ValueXY() - 는 x, y 등 좌표계 이동 등의 애니메이션 동작을 지정할 때 사용합니다.
각각 애니메이션 동작을 어떻게 수행하는지에 대한 메소드입니다.
- decay() - Animates a value from an initial velocity to zero based on a decay coefficient.
감쇠계수를 통해 초기속도에서 0까지 애니메이션화 - spring() - Animates a value according to an analytical spring model based on damped harmonic oscillation.
Tracks velocity state to create fluid motions as the toValue updates, and can be chained together.
특정 속성들을 통해 유동적인 애니메이션을 구현 - timing() - Animates a value along a timed easing curve.
The Easing module has tons of predefined curves, or you can use your own function.
시간 완화 곡선을 따라 값을 애니메이션화
설명이 조금 어려워 각각 직접 테스트해보며 어떻게 애니메이션이 구현되는지 파악해봐야 할 것 같습니다 ...
위 코드로 예시를 들면 onPressIn 함수는 '해당 컴포넌트 스케일을 1에서 0.95로 spring() 방식으로 작동시키겠다'고 정의를 내린 것입니다. 그러면 해당 함수를 앱 인터랙션 부분에서 인터랙션 터치 부분에 넣어주면 해당 컴포넌트는 터치 시 spring() 방식으로 scale 이 1 에서 0.95로 줄어들게 될 것입니다. 이런 부분들을 적재적소에 복합적으로 적용하면 직관적이고 부드러운 애니메이션을 연출할 수 있습니다.
리액트 네이티브에서 화면을 구성하는 컴포넌트들에게 애니메이션 요소를 부여합니다.
위 목록에서 보여주는 바와 같이 <Image /> , <ScrollView /> , <Text /> , <View /> , <FlatList /> , <SectionList /> 등의 컴포넌트에 애니메이션이 동작하도록 씌울수(wrapping) 있습니다.
다음은 위에 설명한 내용들을 바탕으로 chatGPT 가 구현한 간단한 Animated 동작의 예시입니다.
import React, { useState } from 'react';
import { StyleSheet, Text, TouchableOpacity, View, Animated } from 'react-native';
const Example = () => {
const [animation] = useState(new Animated.ValueXY({ x: 0, y: 0 }));
const moveSquare = () => {
Animated.timing(animation, {
toValue: { x: 200, y: 200 },
duration: 1000,
useNativeDriver: false,
}).start();
};
return (
<View style={styles.container}>
<Animated.View style={[styles.box, animation.getLayout()]} />
<TouchableOpacity onPress={moveSquare}>
<Text>Move Square</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 100,
height: 100,
backgroundColor: 'red',
},
});
export default Example;
이 코드에서 animation 변수는 useState(react hook)를 사용하여 Animated.ValueXY 객체를 초기화합니다.
moveSquare 함수는 Animated.timing() 메서드를 사용하여 animation 값을 변경하고 애니메이션을 시작합니다.
getLayout() 메서드는 animation으로 정의된 Animated.ValueXY 객체를 픽셀 단위의 위치 값으로 변환합니다.
Animated.View 컴포넌트는 style 속성에 animation.getLayout()을 전달하여 animation 객체에 의해 제어되는 위치 값을 사용합니다. 이렇게 하면 Animated.View가 애니메이션과 함께 이동하게 됩니다. TouchableOpacity는 버튼을 누르면 moveSquare 함수를 호출하도록 설정됩니다.
Animated.timing() 메서드에서 useNativeDriver: false 라는 속성이 있는데, 이는 말 그래도 네이티브 방식을 따를지를 지정하는 부분입니다.
공식 문서에서 설명하기를
'애니메이션이 시작되기 전 네이티브로 애니메이션에 대한 모든 것을 전송하므로 네이티브 코드가 매 프레임 마다 브리지(bridge)를 거치지 않고 UI Thread에서 애니메이션을 수행할 수 있습니다.'
라고 명시 되어있는데, 좀 더 자세한 설명을 듣기 위해 chatGPT 의 의견을 구해봤습니다.
위 설명에 의하면 'translateX', 'tranlateY', 'scale', 'rotate' 외 몇가지 간단한 속성들은 'useNativeDriver: true' 옵션을 사용하면 브릿지를 통해 JS Thread를 거치지 않고 네이티브 영역에서 처리할 수 있어 더 나은 최적화를 기대할 수 있을 것 같습니다.
다음과 같은 메서드를 이용하여 여러 애니메이션을 제어할수있습니다.
- Animated.delay() - 애니메이션을 시작하기 전에 지정된 시간만큼 딜레이를 추가합니다.
ex)
const opacityValue = useRef(new Animated.Value(1)).current;
const animate = () => {
Animated.sequence([
Animated.delay(3000),
Animated.timing(opacityValue, {
toValue: 0,
duration: 1000,
useNativeDriver: true,
})
]).start();
};
다음과 같이 Animated.timing() 메서드가 실행되기 전 Animated.delay(3000) 를 통해 3초정도 지연시킨뒤 애니메이션을 실행할 수 있습니다.
- Animated.parallel() - 병렬로 여러개의 애니메이션을 실행할 수 있게 합니다.
ex)
const scaleValue = useRef(new Animated.Value(0.5)).current;
const opacityValue = useRef(new Animated.Value(0)).current;
const animate = () => {
Animated.parallel([
Animated.timing(scaleValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(opacityValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
})
]).start();
};
각각 scale (0.5 -> 1) 과 opacity (0 -> 1) 에 대한 애니메이션을 동시에 실행 시킵니다.
- Animated.sequence() - 순차적으로 여러 개의 애니메이션을 실행할 수 있게 해줍니다.
- Animated.stagger() - 여러 개의 애니메이션을 일정한 간격을 두고 실행할 수 있게 해줍니다.
ex)
const scaleValue1 = useRef(new Animated.Value(0.5)).current;
const scaleValue2 = useRef(new Animated.Value(0.5)).current;
const scaleValue3 = useRef(new Animated.Value(0.5)).current;
const animate = () => {
Animated.stagger(200, [
Animated.timing(scaleValue1, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(scaleValue2, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(scaleValue3, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
})
]).start();
};
scaleValue1,2,3 각각의 애니메이션들을 200ms 간격으로 실행시켜 줍니다.
- Animated.add() - 두 개 이상의 애니메이션을 더하는 메서드입니다.
- Animated.subtract() - add() 와 반대로 첫번째 애니메이션에서 두번째 애니메이션 값을 빼는 메서드입니다. value 값의 차이에 의해 width, height 등의 value가 조정되어야 하는 경우에 사용됩니다.
- Animated.divide() - 첫번째 애니메이션을 두번째 애니메이션 값으로 나누는 메서드입니다. subtract()과 마찬가지로 value 값의 등분에 의해 width, height 등의 value가 조정되어야 하는 경우에 사용됩니다.
- Animated.modulo() - 애니메이션 값을 주어진 숫자로 나눈 나머지를 반환하는데 사용됩니다.
- Animated.multiply() - 애니메이션 값을 주어진 숫자로 곱한값을 반환하는데 사용됩니다.
여기서 문득 Animated.add() 와 Animated.paralled() 메서드의 차이에 대해 궁금해졌습니다. 둘다 애니메이션을 동시에 진행시키도록 동작 하기 때문입니다.
공통점은 둘 다 서로 영향을 미치지 않고 병행적으로 수행되지만, add()는 하나의 애니메이션으로 합치고, paralled()은 개별적인 애니메이션이라는 점입니다. 여러 애니메이션들을 잘개 쪼개어 정의해두고 각각 상황에 맡게 add()와 paralled() 메서드를 적재적소에 사용하면 좋을 것 같습니다.
그리고 리액트 네이티브 API에서 제공하는 강력한 기술이 있습니다.
가이드 문서 ) https://reactnative.dev/docs/animations#interpolation
Animations · React Native
Animations are very important to create a great user experience. Stationary objects must overcome inertia as they start moving. Objects in motion have momentum and rarely come to a stop immediately. Animations allow you to convey physically believable moti
reactnative.dev
Interpolation 기술입니다.
한글로는 보간법(interpolation) 이라 하는데, 위키피디아는 다음과 같이 소개하고 있습니다.
수학 용어로 설명이 조금 어려운데, 살짝 풀어서 설명하자면 특정 컴포넌트의 서로다른 어떤 요소(ex. valueX, valueY)를 inputRange 와 outputRange 를 통해 중간값을 추정하는 방법이라 할 수 있겠고, 결과적으로 요소의 변화를 부드럽게 동작하도록 합니다.
위 영상은 해당 코드를 예시로 ios simulator로 동작 시킨 모습입니다. 보다시피 특정 컴포넌트가 드래그 될때 화면 Y축 value가 변함에 따라 borderRadius를 조정하여 모양을 원형으로 바뀌기도 하고 컴포넌트 색상까지도 변경할 수 있습니다.
2. Interaction
공식 DOCUMENT - API PanResponder ) https://reactnative.dev/docs/panresponder
PanResponder · React Native
PanResponder reconciles several touches into a single gesture. It makes single-touch gestures resilient to extra touches, and can be used to recognize basic multi-touch gestures.
reactnative.dev
인터랙션(=상호작용)은 위에서도 설명했듯이 사용자가 단말기기에 어떻게 입력하고 피드백 받는 지의 총제적인 과정을 말합니다.
그 중에서도 PanResponder 기술에 대해 소개해드리겠습니다.
chatGPT에서 알려주듯이 PanResponder 는 리액트 네이티브 터치 인터랙션에서 각종 제스쳐 이벤트를 지원해줍니다.
콜백함수들을 통해 각종 이벤트에 원하는 특정 함수들 (ex. 위에서 소개했던 애니메이션 함수들) 을 넣어 직관적이고 부드러운 사용자 경험(UX)를 제공해 줄 수 있습니다.
위 코드는 앞에서 예시로 들였던 Y축이 이동할때마다 컴포넌트의 모양과 색상이 바뀌는 동작의 PanResponder 의 코드입니다.
onPanResponderGrant : 터치를 처음 감지했을때 작동하는 부분이고,
onPanResponderMove : 드래그를 감지하여 작동,
onPanResponderRelease : 터치를 떼었을 때를 감지하는 부분입니다.
이렇게 설정하고 최종적으로,
다음과 같이 설정한 aimation, interation들을 해당 컴포넌트 속성에 반영하면 위 영상과 같이 작동하는 애니메이션을 구현할 수 있습니다.
위 예시 이외에도
다음과 같은 많은 기능들이 있어서 자세한 부분은 공식 문서를 참조하시면 되겠습니다.
3. 그 외 ...
리액트 네이티브에서는 앞에서 본 것과 같이 자체적으로 제공해주는 API 외에도 여러 라이브러리들이 존재합니다.
- 1. React Native Animated : 앞에서 소개드린 리액트 네이티브에서 자체적으로 제공해주는 API 입니다.
- 2. React Native Reanimated : React Native Animated 위에 돌아가는 라이브러리로 더 복잡한 애니메이션과 상호작용을
좋은 성능으로 효과적으로 구현해준다고 합니다. - 3. Lottie : 리액트 네이티브 앱에 Adobe After Effects 로 만든 애니메이션을 구현할 수 있게하는 라이브러리라고 합니다.
실상 Adobe After Effects 로 제공되는 모션 그래픽등의 애니메이션들은 상당한 고퀄리티로도 제작이 가능해서
기술만 된다면 상당히 복잡한 애니메이션도 구현할 수 있을 것으로 보입니다. - 4. Gesture Handler : 리액트 네이티브에서 복잡한 터치 인터랙션 지원을 제공해 주는 라이브러리 입니다.
- 5. React Native Interactable : 드래그할수있는 카드들이나 스와잎할수있는 리스트들과 같은 요소 인터랙션을 지원해주는
라이브러리라고 합니다.
마무리하며 ...
리액트 네이티브는 React, 그 이전에 JavaScript 기반이기에, 기존의 JS, React animation 기술에 앞에 소개해드린 자체 리액트 네이티브 API나 라이브러리를 활용하여 더 복잡하고 세련된 애니메이션을 구현할 수 있다는 것이 장점이라고 생각됩니다.
적재적소의 애니메이션은 React Native 앱의 사용자 경험(UX)에 있어서 큰 차별성을 줄 수 있고, 몰입감과 더 큰 만족감을 줄 수 있기에 앱 제작에 있어서 필수는 아니더라도 충분히 고려되야 할 부분이라 생각됩니다.