포슀트

πŸŒ’ React Native λ¦¬μ•‘νŠΈ λ„€μ΄ν‹°λΈŒ - Animation

@ μΊλŸ¬μ…€ Carousel

πŸ’« Animation


μ• λ‹ˆλ©”μ΄μ…˜μ€ UI μš”μ†Œκ°€ μž‘μš©ν•  λ•Œ λͺ…ν™•ν•œ ν”Όλ“œλ°±μ„ μ‚¬μš©μžμ—κ²Œ μ œκ³΅ν•œλ‹€.

λ¦¬μ•‘νŠΈ λ„€μ΄ν‹°λΈŒκ°€ μ œκ³΅ν•˜λŠ” κΈ°λŠ₯은 4κ°€μ§€λ‘œ μš”μ•½ν•  수 μžˆλ‹€.

1
import {Animated, Easing, PanResponder, LayoutAnimation} from 'react-native'

πŸ’« νŠΉμ§•


λ¦¬μ•‘νŠΈ λ„€μ΄ν‹°λΈŒ μ• λ‹ˆλ©”μ΄μ…˜μ€ 두 가지 λͺ¨λ“œλ‘œ λ™μž‘ν•œλ‹€.

  • μžλ°”μŠ€ν¬λ¦½νŠΈ 엔진 μ• λ‹ˆλ©”μ΄μ…˜
  • λ„€μ΄ν‹°λΈŒ λͺ¨λ“ˆ μ• λ‹ˆλ©”μ΄μ…˜

λ„€μ΄ν‹°λΈŒ λͺ¨λ“ˆ μ• λ‹ˆλ©”μ΄μ…˜μ„ μ‚¬μš©ν•  것을 κΆŒκ³ ν•œλ‹€.

useNativeDriver 속성을 톡해 μ–΄λ–€ λͺ¨λ“œλ‘œ μ• λ‹ˆλ©”μ΄μ…˜μ„ λ™μž‘μ‹œν‚¬μ§€ κ²°μ •ν•  수 μžˆλ‹€.

λ”°λΌμ„œ λ„€μ΄ν‹°λΈŒ λͺ¨λ“ˆ μ• λ‹ˆλ©”μ΄μ…˜μ„ 기반으둜 ν•˜λ˜, λΆˆκ°€λŠ₯ν•œ 것듀은 (fontSize, …) useNativeDriver 속성을 μ΄μš©ν•˜μ—¬ κ΅¬ν˜„ν•œλ‹€.

πŸ’« Animatedκ°€ μ œκ³΅ν•˜λŠ” μ• λ‹ˆλ©”μ΄μ…˜ κΈ°λŠ₯


  • μ• λ‹ˆλ©”μ΄μ…˜ 보간값
    • Value
    • ValueXY
  • 단일 μ• λ‹ˆλ©”μ΄μ…˜ μ œμ–΄
    • timing()
    • spring()
    • decay()
    • delay()
    • loop()
  • μ—¬λŸ¬ 개의 μ• λ‹ˆλ©”μ΄μ…˜ 톡합 μ œμ–΄
    • sequence()
    • parallel()
    • stagger()
  • μ• λ‹ˆλ©”μ΄μ…˜ μ—°μ‚°
    • add()
    • substract()
    • multiply()
    • divide()
    • modulo()
    • diffClamp
  • μ• λ‹ˆλ©”μ΄μ…˜ 이벀트
    • event()
  • μ• λ‹ˆλ©”μ΄μ…˜ λŒ€μƒ μ»΄ν¬λ„ŒνŠΈ
    • View
    • Image
    • Text
    • ScrollView
    • FlatList
    • SectionList

🫧 Value 클래슀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
export class Value
{
	constructor(value: number);
	setValue(value: numbe): void;

	// 콜백 ν•¨μˆ˜λ₯Ό 톡해 ν˜„μž¬ 보간 쀑인 값을 얻을 수 μžˆλ‹€.
	// useEffectμ—μ„œ add/remove ν•˜λŠ” 식
	addListener(callback: ValueListenderCallback): string;
	removeListener(id: string): void;
	removeAllListeners(): void;

	// μž…λ ₯ 보간 값을 μƒˆλ‘œμš΄ λ³΄κ°„κ°’μœΌλ‘œ λ°”κΏ€ 수 μžˆλ‹€.
	// i.e. 좜λ ₯을 0 ~ 100, Red ~ Blue, 0deg ~ 360deg
	interpolate(config: InterpolationConfigType): AnimatedInterpolation;
	// animValue.interpolate({inputRange: [0, 1], outputRange: [0, 100]})
	// animValue.interpolate({inputRange: [0, 1], outputRange: ['red', 'blue']})
	// animValue.interpolate({inputRange: [0, 1], outputRange: ['0deg', '360deg']})
	// animValue.interpolate({inputRange: [0, 0.7, 1], outputRange: [Colors.lightBlue900, Colors.lime500, Colors.pink500]})

	// ~
}

type ValueListenerCallback = (stage: {value: number}) => void;

class AnimatedInterpolation
{
	interpolate(config: InterpolationConfigType): AnimatedInterpolation;
}

// inputRangeλ₯Ό λ²—μ–΄λ‚œ 값이 λ°œμƒν–ˆμ„ λ•Œ μ–΄λ–€ κ°’μœΌλ‘œ outputRangeλ₯Ό λ§Œλ“€μ§€ κ²°μ •ν•˜λŠ” 속성
// clamp : κ°’ λ¬΄μ‹œ
// extend : λ²”μœ„ λ‚΄ 값을 κ³„μ‚°ν•œ 곡식을 λ²”μœ„ μ™Έ 값에도 λ˜‘κ°™μ΄ 적용
// identity : μ–΄λ–€ 곡식도 μ μš©ν•˜μ§€ μ•Šκ³  μž…λ ₯κ°’ κ·ΈλŒ€λ‘œ 좜λ ₯
type ExtrapolateType = 'extend' | 'identity' | 'clamp';

type InterpolationConfigType = 
{
	inputRange: number[];
	outputRange: number[] | string[];
	
	// Like Animated.timing
	easing?: (input: number) => number;
};

πŸ’« λ™μž‘ 원리


λ¦¬μ•‘νŠΈ λ„€μ΄ν‹°λΈŒ μ• λ‹ˆλ©”μ΄μ…˜μ€ CSS μ• λ‹ˆλ©”μ΄μ…˜κ³Ό 같은 κ°œλ…μ΄λ‹€.

CSS μ• λ‹ˆλ©”μ΄μ…˜μ€, transitionμ΄λ‚˜ animate μŠ€νƒ€μΌ 속성에 μ• λ‹ˆλ©”μ΄μ…˜μ„ μ μš©ν•˜κ³  싢은 λ‹€λ₯Έ μŠ€νƒ€μΌ 속성값을 μ‘°μ •ν•˜λŠ” λ°©μ‹μœΌλ‘œ λ™μž‘ν•œλ‹€.

λ¦¬μ•‘νŠΈ λ„€μ΄ν‹°λΈŒ μ• λ‹ˆλ©”μ΄μ…˜μ€, style 속성에 μ„€μ •ν•˜λŠ” opacity, transform λ“±μ˜ μŠ€νƒ€μΌ 속성에 λ³΄κ°„ν•œ 값을 μ €μž₯ν•˜λŠ” Animated.Value 클래슀 객체(μΈμŠ€ν„΄μŠ€)λ₯Ό μ„€μ •ν•˜λŠ” λ°©μ‹μœΌλ‘œ λ™μž‘ν•œλ‹€.

πŸ’« κ΅¬ν˜„


🫧 Animated.Value 클래슀의 μΈμŠ€ν„΄μŠ€ 생성

Animated.Value 클래슀의 μΈμŠ€ν„΄μŠ€ μƒμ„±μœΌλ‘œ μ‹œμž‘ν•΄λ„ λ˜μ§€λ§Œ,

λ¦¬μ•‘νŠΈ λ„€μ΄ν‹°λΈŒ νŒ€μ€ useRef 훅을 μ‚¬μš©ν•˜μ—¬ Animated.Value 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό μΊμ‹œν•˜λŠ” 방법을 ꢌμž₯ν•œλ‹€.

1
2
3
4
5
// O
const animValue = new Animated.Value(0)

// O (ꢌμž₯)
const animValue = useRef(new Animated.Value(0)).current

useRef을 μ‚¬μš©ν•˜λ©΄, animValueλ₯Ό 단 ν•œ 번만 μƒμ„±ν•˜κ³  μž¬λ Œλ”λ§ μ‹œ μž¬μ‚¬μš©ν•œλ‹€.

🫧 Animated.Value 클래슀의 μΈμŠ€ν„΄μŠ€ 적용

Animated.Value 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό μ»΄ν¬λ„ŒνŠΈμ˜ μŠ€νƒ€μΌ 속성에 μ μš©ν•œλ‹€.

1
const someViewAnimStyle= {opacity: animValue}

opacity μ†μ„±μ˜ νƒ€μž…μ΄ numberκ°€ μ•„λ‹ˆλΌ Animated.Value νƒ€μž…μ΄λ―€λ‘œ View 같은 μ»΄ν¬λ„ŒνŠΈλŠ” 이λ₯Ό 해석할 수 μ—†λ‹€.

λ•Œλ¬Έμ— Animated.View 같은 μ»΄ν¬λ„ŒνŠΈλ₯Ό μ΄μš©ν•˜μ—¬ μŠ€νƒ€μΌ 속성 섀정값이 Animated.Value νƒ€μž… 객체인 μŠ€νƒ€μΌ 속성을 μ²˜λ¦¬ν•  수 있게 ν•œλ‹€.

1
<Animated.View style={[styles.someView, someViewAnimStyle]}>

🫧 μ• λ‹ˆλ©”μ΄μ…˜ μž¬μƒ

μ• λ‹ˆλ©”μ΄μ…˜μ„ μž¬μƒμ‹œν‚€λ €λ©΄ onPress λ“±μ—μ„œ μ½”λ“œλ₯Ό μ‹€ν–‰ν•΄μ•Ό ν•œλ‹€.

1
2
3
4
const onPress = () =>
{
	Animated.timing(animValue, {toValue:1, uesNativeDriver: true, duration: 1000}).start()
}

🫧 useRef ν›…κ³Ό MutableRefObject νƒ€μž…

useRef 훅은 RefObject<T> λ˜λŠ” MutableRefObject<T> 을 λ°˜ν™˜ν•  수 μžˆλ‹€.

1
function useRef<T>(initialValue: T): MutableRefObject<T>
MutableRefObject μ œλ„€λ¦­ νƒ€μž…μ—λŠ” λ‹€μŒ RefObject νƒ€μž…μ²˜λŸΌ currentλΌλŠ” 속성이 μžˆλ‹€. λ‹€λ§Œ current의 νƒ€μž…μ€ Tnull이 μ•„λ‹ˆλΌ T이닀. 즉, currenλŠ” null이 될 수 μ—†λ‹€.
1
2
3
4
interface MutableRefObject<T>
{
	current: T;
}

κ·ΈλŸ¬λ―€λ‘œ animValueλŠ” null이 될 수 μ—†μœΌλ©° λ³€ν•˜μ§€λ„ μ•ŠλŠ”λ‹€. λ•Œλ¬Έμ— ꡳ이 animValueλ₯Ό useMemo, useCallback의 μ˜μ‘΄μ„± λͺ©λ‘μ— μΆ”κ°€ν•  ν•„μš”κ°€ μ—†λ‹€.

animValueκ°€ μ•„λ‹ˆλΌ, animValue λ‚΄λΆ€μ˜ value μ†μ„±μ˜ 값이 보간에 μ˜ν•΄ 0~1둜 λ³€ν•˜λŠ” 것이닀.

1
const animValue = useRef(new Animted.Value(0)).current;

🫧 Animated.View와 Animated.createAnimatedComponent ν•¨μˆ˜

Animated.createAnimatedComponent ν•¨μˆ˜λŠ” λ‹€λ₯Έ μ»΄ν¬λ„ŒνŠΈλ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ μž…λ ₯λ°›μ•„ Animated.Value νƒ€μž… 객체λ₯Ό μ²˜λ¦¬ν•  수 μžˆλŠ” κΈ°λŠ₯을 κ°€μ§€λŠ” μƒˆλ‘œμš΄ μ»΄ν¬λ„ŒνŠΈλ₯Ό λ§Œλ“ λ‹€.

자주 μ“°μ΄λŠ” View, Text, ImageλŠ” ꡳ이 μƒμ„±ν•˜μ§€ μ•Šμ•„λ„ λ°”λ‘œ μ‚¬μš©ν•  수 μžˆλ„λ‘ μ»΄ν¬λ„ŒνŠΈλ₯Ό μ œκ³΅ν•œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
type AnimatedComponent = Animated.createAnimatedComponent
export function createAnimatedComponent<T>(component: T): AnimatedComponent<T>;

Animated.View
// Animated.createAnimatedComponent(View)

Animated.Text
// Animated.createAnimatedComponent(Text)

Animated.Image
// Animated.createAnimatedComponent(Image)

🫧 Animated.timing

Animated.timing은 value와 configλ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ λ°›μ•„ Animated.CompositeAnimation νƒ€μž… 객체λ₯Ό λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜μ΄λ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
export const Animated.timing:
(
	value: Animated.Value | Animated.ValueXY,
	config: Animated.TimingAnimationConfig
) => Animated.ComposteAnimation;

// Animated.TimingAnimationConfig
interface AnimationConfig
{
	useNativeDriver: boolean;
}
interface TimingAnimationConfig extends AnimationConfig
{
	toValue: number | Animated.Value // new Animated.Value(μ‹œμž‘κ°’)의 끝값 μ„€μ •
	duration?: number // μ• λ‹ˆλ©”μ΄μ…˜ 진행 μ‹œκ°„ (millisecond)
	delay?: number // μ• λ‹ˆλ©”μ΄μ…˜ 진행 μ „ λŒ€κΈ° μ‹œκ°„
	easing?: (value: number) => nuber; // Easing이 μ‚¬μš©ν•˜λŠ” 보간 ν•¨μˆ˜
}

// Easing
export type EasingFunction = (value: number) => number;
export interface Easing
{
	linear: EasingFunction;
	ease: EasingFunction;
	// ~
}

// CompositeAnimation
export interface CompositeAnimation
{
	start: (callback?: EndCallback) => void;
	// ~
}
type EndResult = {finished: boolean};
type EndCallback = (result: EndResult) => void;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// i.e.
Animated.timing
(
	// λŒ€μƒ
	animValue,
	// μ• λ‹ˆλ©”μ΄μ…˜
	{
		useNativeDriver: true,
		toValue: show ? 0 : 1,
		duration: 1000,
		easing: Easing.bounce
	}
).start(
	(result: {finished: boolean}) => console.log(result)
	)

// result λ§€κ°œλ³€μˆ˜λŠ” 항상 {finished: true} μ΄λ―€λ‘œ () => console.log('animation end') 같이 κ΅¬ν˜„ν•΄λ„ μ’‹λ‹€

🫧 ransform Animation

@ μˆ˜μ—… 쀑 μƒλž΅

🫧 Animated μ—°μ‚° κ΄€λ ¨ ν•¨μˆ˜

1
2
3
4
5
6
7
8
type Value = Animated.Value
export function add(a: Value, b: Value): Animated.AnimatedInterpolation // +
export function substract(a: Value, b: Value): Animated.AnimatedInterpolation // -
export function multiply(a: Value, b: Value): Animated.AnimatedInterpolation // *
export function divide(a: Value, b: Value): Animated.AnimatedInterpolation // /
export function modulo(a: Value, b: Value): Animated.AnimatedInterpolation // %

// λ§€κ°œλ³€μˆ˜κ°€ numberκ°€ μ•„λ‹Œ Animated.Valueμž„μ„ 주의
이 κΈ°μ‚¬λŠ” μ €μž‘κΆŒμžμ˜ CC BY 4.0 λΌμ΄μ„ΌμŠ€λ₯Ό λ”°λ¦…λ‹ˆλ‹€.