
React Native에서 애니메이션을 구현할 때에는 Animated라는 객체를 사용합니다.
Reanimated라는 META에서 배포한 서드파티 애니메이션 라이브러리도 있는데, 기본적으로 리액트 네이티브에 내장되어 있는 라이브러리는 Animated입니다. 두 라이브러리는 애니메이션을 동일하게 애니메이션을 구현할 수 있게 해주지만, Reanimated가 성능 면에서 좀 더 좋은 모습을 보여주고 개발하기 좀 더 편합니다.
간단한 애니메이션을 구현할 때에는 주로 리액트 네이티브의 Animated를 사용합니다.
이 글에서는 Animated를 사용한 애니메이션 구현에 대해 다루고, 다음 시간에 Reanimated에 대해서 다뤄보려고 합니다.
Animated를 사용하여 애니메이션을 구현할 때에는 Animated 객체의 Value라는 애니메이션 값을 하나 만들어줘야 합니다. 이제부터 이 Value 값의 조작을 통해 애니메이션을 구현할 수 있게 됩니다.
Value를 만들 때에는 useRef를 사용하여 컴포넌트가 사라질 때까지 재사용할 수 있도록 해주고, Value 생성자 함수의 인자에는 Value의 초깃값을 넣어 사용합니다.
다음 예시는 animation이라는 Value를 생성하는 코드입니다.
animation 변수에 할당한 Ref 객체는 Animated 객체의 Value 속성의 값을 가리키게 됩니다. 이때 Value의 값은 1입니다.
import React, {useRef} from 'react';
import {Animated} from 'react-native';
function Sample() {
const animation = useRef(new Animated.Value(1)).current;
}
이렇게 초기화한 값을 리액트 컴포넌트의 스타일에 적용할 때에는 Animated. 뒤에 View, Text, Image 처럼 사용하고 싶은 리액트 네이티브 컴포넌트의 이름을 지정하면 됩니다. 즉, 애니메이팅 시킬 리액트 네이티브 컴포넌트를 지정하면 됩니다.
style에 적용하는 값은 어떤 애니메이션을 적용하는지에 따라 달라집니다.
이 예시에서는 투명도를 조절하는 opacity로 설정하고 있습니다.
<Animated.View style={{opacity: animation}}></Animated.View>
<Animated.Text style={{opacity: animation}}></Animated.View>
<Animated.Image style={{opacity: animation}}></Animated.View>
Value를 변경할 때에는 Animated.timing이라는 Animated 내장 함수를 사용합니다.
예시 코드에는 타겟 값으로 animation이 가리키고 있는 값(ref.current)으로 설정했습니다.
Animated.timg(타겟값, 속성객체).start(콜백함수);
Animated.timing(animation, {
toValue: 0, // 어떤 값으로 변경할 것인가: 필수
duration: 1000, // 애니메이션에 걸리는 시간(ms)
// 기본값: 500
delay: 0, // 딜레이 이후 애니메이션 시작(ms)
// 기본값: 0
useNativeDriver: true, // 네이티브 드라이버 사용 여부: 필수
isInteraction: true, // 사용자 인터랙션에 의해 시작한 애니메이션인가 // 기본값: true
easing: Easing.inOut(Easing.ease), // 애니메이션 속도 변경 함수
// 기본값: Easing.inOut(Easing.ease)
}).start(() => {
// 애니메이션 처리 완료 후 실행할 작업
})
여기서 toValue, useNativeDriver 값은 필수 값입니다.
useNativeDriver는 애니메이션 처리 작업을 자바스크립트 엔진이 아닌 네이티브 레벨에서 진행하게 하는 옵션으로, trasfrom, opacity처럼 레이아웃과 관련 없는 스타일에만 적용할 수 있습니다.
예를 들어, 레이아웃에 영향을 끼치는 left, width, paddingLeft, marginLeft등과 같은 스타일에는 꼭 useNativeDriver를 false로 지정해야 합니다.
애니메이션이 끝난 후에 실행 시킬 함수는 .start()로 실행시킬 수 있습니다.
이 함수에 콜백 함수를 인자로 넣어주면 애니메이션이 끝난 후 호출됩니다.
,...
}).start(() => {
console.log('done!'); // 애니메이션이 끝난 후 'done!' 출력
})
UI 컴포넌트의 투명도를 조절하는 Fade-in, Fade-out 애니메이션을 만들어보겠습니다.
투명도를 조절하려면 Animated 객체의 Value 값을 0~1 사이로 조절하고, 리액트 네이티브 컴포넌트의 스타일로 opcity를 설정하면 됩니다.
import React, { useRef } from 'react'
import { Animated, StyleSheet, Button, View } from 'react-native'
const FadeInAndOut = () => {
const animation = useRef(new Animated.Value(1)).current
return (
<>
<Animated.View style={[styles.rectangle, { opacity: animation }]} />
<Button
title="FadeIn"
onPress={() => {
Animated.timing(animation, {
toValue: 1,
useNativeDriver: true,
}).start()
}}
/>
<Button
title="FadeOut"
onPress={() => {
Animated.timing(animation, {
toValue: 0,
useNativeDriver: true,
}).start()
}}
/>
</>
)
}
export default function MyScreen() {
return (
<View style={styles.container}>
<FadeInAndOut />
</View>
)
}
MyScreen에서 사용할 FadeInAndOut 함수를 만들었습니다. 함수에서 각각의 버튼을 클릭하면 toValue 속성을 0과 1로 전환시키고, animation 객체를 존재(1)했다가 서서히 사라지게(0)합니다. 서서히 사라지는 이유는 timing 메서드의 속성에 duration이 있기 때문입니다.

만약 상태 값에 따라 이 애니메이션을 적용하고 싶다면 useState와 useEffect를 사용하면 됩니다.
이 편이 좀 더 괜찮은 코드라고 볼 수 있겠죠?
const FadeInAndOut = () => {
const animation = useRef(new Animated.Value(1)).current
const [hidden, setHidden] = useState(false)
useEffect(() => {
Animated.timing(animation, {
toValue: hidden ? 0 : 1,
useNativeDriver: true,
}).start()
}, [hidden, animation])
return (
<View>
<Animated.View style={[styles.rectangle, { opacity: animation }]} />
<Button title="toggle" onPress={() => setHidden(!hidden)} />
</View>
)
}

Boolean 타입의 값을 토글하는 버튼으로 hidden state를 변경하여 개체의 투명도를 조절합니다.
여기서 주의할 것은, 토글 버튼이 animation을 조작하는 것이 아닌 상태 값만 바꿔주고 있다는 것입니다.
useEffect를 통해 hidden 값의 변화를 계속 감시하기 때문에 애니메이션이 발생합니다.
UI 컴포넌트를 움직이게할 때에는 transfrom 스타일을 사용하면 됩니다.
transform은 X, Y 좌표를 사용하여 컴포넌트를 이동시킬 때 사용합니다.
예를 들어 기존에 있던 위치에서 우측으로 100, 아래로 50만큼 움직이고 싶다면 다음과 같이 스타일을 적용하면 됩니다.
위로 움직이거나 좌측으로 움직이고 싶다면 음수로 설정하면 됩니다.
{
transform: [{translateX: 100}, {translateY: 50}] // 우측으로 100, 아래로 50
},
{
transform: [{translateX: -100}, {translateY: -50}] // 좌측으로 100, 위로 50
}
const SlideLeftAndRight = () => {
const animation = useRef(new Animated.Value(0)).current
const [enabled, setEnabled] = useState(false)
useEffect(() => {
Animated.timing(animation, {
toValue: enabled ? 150 : 0, // enable 값에 따라 우측으로 150만큼 움직입니다
useNativeDriver: true,
}).start()
}, [enabled, animation])
return (
<View>
<Animated.View
style={[styles.rectangle, { transform: [{ translateX: animation }] }]}
/>
<Button title="toggle" onPress={() => setEnabled(!enabled)} />
</View>
)
}

버튼을 누르면 UI 컴포넌트가 우측으로 움직이면서 Fade-out되는 효과를 내고 싶다고 해봅시다.
이처럼 여러 애니메이션 스타일을 적용해야할 때에는 interpolate라는 함수를 사용합니다.
이 함수를 사용하면 Animated객체의 Value 속성이 지니고 있는 값을 기준으로 새로운 값을 생성할 수 있습니다.
interpolate 함수는 다음과 같이 사용합니다.
anmation.interpolate({
inputRange: [0, 1],
outputRange: [0, 150],
})
Value가 지닐 값의 입력 범위와 출력 범위를 지정하면 이에 따라 새로운 값이 생성됩니다.
위 예시와 같이 설정하면 Value가 지닌 값이 0일 때에는 0, 1일 때에는 150으로 지정됩니다.
const SlideLeftAndRight = () => {
const animation = useRef(new Animated.Value(0)).current
const [enabled, setEnabled] = useState(false)
useEffect(() => {
Animated.timing(animation, {
toValue: enabled ? 1 : 0,
useNativeDriver: true,
}).start()
}, [enabled, animation])
return (
<View>
<Animated.View
style={[
styles.rectangle,
{
transform: [
{
translateX: animation.interpolate({
inputRange: [0, 1],
outputRange: [0, 150],
}),
},
],
opacity: animation.interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
}),
},
]}
/>
<Button title="toggle" onPress={() => setEnabled(!enabled)} />
</View>
)
}

토글 버튼을 통해 enable 값을 전환하도록 하고, enable이 true일 때 Value 값을 1로 변경합니다.
그 후interpolate 함수를 통해 설정한 transform과 opacity 스타일을 enable의 값에 따라 달라지게 설정합니다.
transform의 경우 현재 Value 값이 0일 때에는 0, 1일 때에는 150으로 지정됩니다.
opacity의 경우 현재 Value 값이 0일 때에는 1, 1일 때에는 0으로 지정됩니다.
기존에는 timing 함수로 애니메이션 효과를 적용했는데, 이 외에도 spring이라는 함수가 있습니다.
timing과 비슷한데, 값이 단순히 toValue로 지정한 값이 서서히 변하는 것이 아니라 스프링처럼 통통 튀는 효과가 나타납니다.
예를 들어, 0에서 1로 설정한다면 다음과 같이 수치가 변경됩니다.
0 -> 1.2 -> 0.9 -> 1.1 -> 1
목푯값에 도달했을 때 멈추는 것이 아니라, 스프링이 눌렸다 다시 펴질 때처럼 목푯값 근처에 왔다 갔다 합니다. (oscilate)
spring 함수에는 다음과 같은 옵션을 설정해줄 수 있습니다.
Animated.spring(animation, {
toValue: enable ? 1: 0,
useNativeDriver: true,
tension: 45, // 강도 - 기본 값 40
friction: 5, // 감속 - 기본 값 7
speed: 12, // 속도 - 기본 값 12
bounciness: 8 // 탄력성 - 기본 값 8
}).start()
이 옵션을 사용할 때 tension, friction을 같이 사용하거나 speed, bounciness를 같이 사용할 수 있지만 다른 조합으로는 사용할 수 없습니다.
리액트 네이티브를 다루는 기술, 김민준