Animated로 애니메이션 적용하기
Animated 사용법
리액트 네이비트에서 애니메이션을 구현할 때는 Animated라는 객체를 사용한다.
일단 Animated를 사용하려면 Value를 하나 만들어야 한다.
import React, {useRef} from 'react';
import {Animated} from 'react-native';
function Sample() {
const animation = useRef(new Animated.Value(1)).current;
}
Value를 만들 때는 이렇게 useRef를 사용해야 한다. 래퍼런스 선택 외에 특정 값을 컴포넌트 생성 시에 설정하고, 컴포넌트가 사라질 때까지 재사용하고 싶은 경우에도 이와 같이 useRef를 사용해 구현할 수 있다.
Value의 생성자 함수 인자에는 초깃값을 넣어준다. 그리고 이 값을 리액트 컴포넌트의 스타일에 적용할 때는 다음과 같이 사용한다.
<Animated.View style={{opacity: animation}}></Animated.View>
즉, Animated. 뒤에 사용하고 싶은 리액트 네이티브 컴포넌트의 이름을 넣어주면 된다.
이 예시 코드에서는 컴포넌트의 투명도 값을 animation이 가리키고 있는 값으로 설정했다. 추후 이 값을 변경할 때는 Animated.timing이라는 함수를 사용한다.
Animated.timing(animation, {
toValue: 0, // 어떤 값으로 변경할지 - 필수
duration: 1000, // 애니메이션에 걸리는 시간(밀리세컨드) - 기본값 500
delay: 0, // 딜레이 이후 애니메이션 시작 - 기본값 0
useNativeDriver: true, // 네이티브 드라이버 사용 여부 - 필수
isInteraction: true, // 사용자 인터랙션에 의해 시작한 애니메이션인지 지정 - 기본값 true
easing: Easing.inOut(Easing.ease), // 애니메이션 속서 변경 함수 - 기본값 Easing.inOut(Easing.ease)
}).start(() => {
// 애니메이션 처리 완료 후 실행할 작업
})
useNativeDriver는 애니메이션 처리 작업을 자바스크립트 엔진이 아닌 네이티브 레벨에서 진행하게 하는 옵션으로 transform, opacity처럼 레이아웃과 관련없는 스타일에만 적용할 수 있다. 예를 들어 레이아웃에 영향을 끼치는 left, width, paddingLeft, marginLeft와 같은 스타일에는 꼭 useNativeDriver를 false로 지정해야 한다.
애니메이션은 .start()로 시작하고, 이 함수에 콜백 함수를 인자로 넣어주면 애니메이션이 끝난 후 호출한다.
Fade In, Fade Out
import React, { useRef } from "react";
import { StyleSheet, View, Animated, Button } from "react-native";
function FaceInAndOut() {
const animation = useRef(new Animated.Value(1)).current;
return (
<View>
<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();
}} />
</View>
);
}
function Sample() {
return (
<View style={styles.block}>
<FaceInAndOut />
</View>
)
}
const styles = StyleSheet.create({
block: {},
rectangle: {
width: 100,
height: 100,
backgroundColor: 'black',
},
})
export default Sample;
FadeIn 버튼을 누르면 Animated.View에 설정한 opacity가 1로 변경되고, FadeOut 버튼을 누르면 Animated.View에 설정한 opacity가 0로 변경되는 것이 애니메이션으로 나타나는 것을 확인할 수 있다.
토글 버튼을 만들고 싶을 때는 useState와 useEffect를 사용해 구현할 수 도 있다.
import React, { useEffect, useRef, useState } from "react";
import { StyleSheet, View, Animated, Button } from "react-native";
function FaceInAndOut() {
const animation = useRef(new Animated.Value(1)).current;
const [hidden, setHidden] = useState(true);
useEffect(() => {
Animated.timing(animation, {
toValue: hidden ? 0 : 1,
useNativeDriver: true,
}).start();
}, [hidden, animation]);
return (
<View>
<Animated.View style={[styles.rectangle, { opacity: animation }]} />
<Button
title="FadeInAndOut"
onPress={() => {
setHidden(!hidden)
}} />
</View>
);
}
...
좌우로 움직이기
컴포넌트를 움직일 때는 꼭 필요한 상황이 아니라면 left, top 스타일보다는 transform 스타일을 사용하는 것이 성능면에서 더 좋다.
transform을 사용해 우측으로 100, 아래로 50 움직이고 싶다면 아래와 같이 적용하면 된다.
{ transform: [{translateX: 100}, {translateY: 50}] }
아래는 특정 영역은 useState와 useEffect를 사용해 좌우로 움직이도록 애니메이션을 설정해보았다.
import React, { useEffect, useRef, useState } from "react";
import { StyleSheet, View, Animated, Button } from "react-native";
function SlideLeftAndRight() {
const animation = useRef(new Animated.Value(0)).current;
const [enabled, setEnabled] = useState(false);
useEffect(() => {
Animated.timing(animation, {
toValue: enabled ? 150 : 0,
useNativeDriver: true,
}).start();
}, [animation, enabled]);
return (
<View>
<Animated.View
style={[
styles.rectangle,
{ transform: [{translateX: animation}] }
]}/>
<Button
title="Toggle"
onPress={() => { setEnabled(!enabled) }}
/>
</View>
);
}
function Sample() {
return (
<View style={styles.block}>
<SlideLeftAndRight />
</View>
)
}
const styles = StyleSheet.create({
block: {},
rectangle: {
width: 100,
height: 100,
backgroundColor: 'black',
},
})
export default Sample;
iterpolate라는 함수를 사용하면 Value가 지니고 있는 값을 기준으로 새로운 값을 생성할 수 도 있다.
Value가 지닐 값의 임력 범위와 출력 범위를 지정하면 이에 따라 새로운 값이 생성된다.
function SlideLeftAndRight() {
...
return (
<View>
<Animated.View
style={[
styles.rectangle,
{
transform: [{
translateX: animation.interpolate({
// Value의 값이 0일때는 0, 1일때는 150
inputRange: [0, 1],
outputRange: [0, 150],
})
}],
opacity: animation.interpolate({
// Value의 값이 0일때는 1, 1일때는 0
inputRange: [0, 1],
outputRange: [1, 0],
}),
}
]}/>
<Button
title="Toggle"
onPress={() => { setEnabled(!enabled) }}
/>
</View>
);
}
위 예제 코드를 보면 Interpolate 함수를 통해 Value가 0일때는 translateX: 0, opacity: 1이 되고, Value가 1일때는 translateX: 150, opacity: 0이 되도록 설정하였다.
spring 사용하기
기존에는 timing 함수로 애니메이션 효과를 적용했는데, 이외에도 spring이라는 함수가 있다.
timing과 비슷한데 값이 단순히 toValue로 지정한 값으로 서서히 변하는 것이 아니라, 마치 스프링 처럼 통통 튀는 효과가 나타난다.
예를 들어 0에서 1로 설정한다면 0 -> 1.2 -> 0.9 -> 1.1 -> 1 같이 수치가 변경된다.
useEffect(() => {
Animated.spring(animation, {
toValue: hidden ? 0 : 1,
useNativeDriver: true,
tension: 45,
friction: 5,
}).start()
}, [animation, hidden]);
spring 함수에서는 다음과 같은 옵션을 설정해줄 수 있다.
- tension : 강도 (default: 40)
- friction : 감속 (default: 7)
- speed : 속도 (default: 12)
- bounciness : 탄력성 (default: 8)
이 옵션을 사용할 때 tension/friction을 같이 사용하거나 speed/bounciness를 같이 사용할 수 있지만 다른 조합으로는 사용하지 못한다.
이 옵션 외에도 다른 옵션들이 있으니 자세한 내용은 아래 링크를 참조하면 된다.