React Native Animation

hwakyungChoi·2021년 9월 23일
3

애니메이션

애니메이션은 훌륭한 사용자 환경을 만들기 위해 매우 중요합니다. 정지한 물체는 움직이기 시작할 때 관성을 극복해야 합니다 움직이는 물체는 운동량을 가지고 있고 즉시 멈추는 경우는 거의 없습니다. 애니메이션을 사용하면 인터페이스에서 물리적으로 신뢰할 수 있는 동작을 전달할 수 있습니다.

React Native는 두 가지 보완 애니메이션 시스템을 제공합니다.

  • 특정 값에 대한 세분화 및 대화형 제어를 위해 애니메이션
  • 전역 레이아웃 트랜잭션을 위한 LayoutAnimation

Animated API

애니메이션 API는 매우 다양한 흥미로운 애니메이션 및 상호작용 패턴을 매우 유용한 방식으로 간결하게 표현하도록 설계되었습니다. 애니메이션은 입력과 출력 사이의 선언적 관계, 그 사이에 구성 가능한 변환 및 시간 기반 애니메이션 실행을 제어하는 시작/중지 방법에 초점을 맞춥니다.

애니메이션은 애니메이션 적용 가능한 6가지 구성 요소 유형을 내보냅니다. View, Text, Image, ScrollView, FlatList 및 SectionList 을 사용할 수도 있지만 Animated.createAnimatedComponent()를 사용하여 직접 만들 수도 있습니다.

예를 들어, 컨테이너가 마운트될 때 사라지는 컨테이너 보기는 다음과 같이 보일 수 있습니다.

여기서 무슨 일이 일어나고 있는지 설명해 봅시다. FadeInView 생성자에서 fadeAnim으로 불리는 Animated.Value이라는 값이 상태의 일부로 초기화됩니다. View의 불투명도 속성이 이 애니메이션 값에 매핑됩니다. 장면 뒤에는 숫자 값이 추출되어 불투명도를 설정하는 데 사용됩니다.

구성 요소가 마운트되면 불투명도는 0으로 설정됩니다. 그런 다음 fadeAnim 값에서 완화 애니메이션이 시작됩니다. 애니메이션 값은 값이 최종 값 1로 애니메이션될 때 각 프레임의 모든 종속 매핑(이 경우 불투명도만)을 업데이트합니다.

이 작업은 setState를 호출하고 다시 렌더링하는 것보다 더 빠른 최적화된 방식으로 수행됩니다. 전체 구성이 선언적이므로 구성을 직렬화하고 우선 순위가 높은 스레드에서 애니메이션을 실행하는 추가 최적화를 구현할 수 있습니다.

애니메이션 구성

애니메이션은 구성이 매우 용이합니다. 애니메이션 유형에 따라 사용자 정의 및 미리 정의된 완화 기능, 지연, 지속 시간, 붕괴 계수, 스프링 상수 등을 모두 조정할 수 있습니다.

애니메이션은 여러 애니메이션 유형을 제공하며, 가장 일반적으로 사용되는 애니메이션은 Animated.timing()입니다. 미리 정의된 다양한 완화 기능 중 하나를 사용하여 시간 경과에 따른 값 애니메이션을 지원하거나 사용자가 원하는 값을 사용할 수 있습니다. 완화 기능은 일반적으로 애니메이션에서 물체의 점진적인 가속과 감속을 전달하기 위해 사용된다.

기본적으로 타이밍은 최대 속도로 점진적인 가속을 전달하고 점진적으로 감속하여 정지하는 easeInOut 곡선을 사용합니다. 완화 매개 변수를 전달하여 다른 완화 함수를 지정할 수 있습니다. 애니메이션 시작 전 사용자 지정 기간 또는 지연도 지원됩니다.

예를 들어, 최종 위치로 이동하기 전에 살짝 백업하는 개체의 2초 길이의 애니메이션을 만들고자 하는 경우:

Animated.timing(this.state.xPosition, {
  toValue: 100,
  easing: Easing.back(),
  duration: 2000
}).start();

내장된 애니메이션에서 지원되는 모든 구성 매개 변수에 대해 자세히 알아보려면 애니메이션 API 참조의 애니메이션 구성 섹션을 참조하시기 바랍니다.

애니메이션 작성

애니메이션은 순차적으로 또는 병렬로 조합하여 재생할 수 있습니다. Sequential 애니메이션은 이전 애니메이션이 완료된 후 즉시 재생하거나 지정된 지연 후 시작할 수 있습니다. 애니메이션 API는 sequence()와 delay()과 같은 몇 가지 방법을 제공하며, 각각은 일련의 애니메이션을 실행하며 필요에 따라 자동으로 start()/stop()을 호출합니다.

예를 들어, 다음 애니메이션은 정지한 후 평행으로 회전하면서 다시 튀어오릅니다.

Animated.sequence([
  // decay, then spring to start and twirl
  Animated.decay(position, {
    // coast to a stop
    velocity: { x: gestureState.vx, y: gestureState.vy }, // velocity from gesture release
    deceleration: 0.997
  }),
  Animated.parallel([
    // after decay, in parallel:
    Animated.spring(position, {
      toValue: { x: 0, y: 0 } // return to start
    }),
    Animated.timing(twirl, {
      // and twirl
      toValue: 360
    })
  ])
]).start(); // start the sequence group

하나의 애니메이션이 중지되거나 중단되면 그룹의 다른 모든 애니메이션도 중지됩니다. Animated.parallel에는 멈춤이 있습니다.이 옵션을 사용하지 않도록 false로 설정할 수 있습니다.

구성 방법의 전체 목록은 애니메이션 API 참조의 애니메이션 구성 섹션에서 확인할 수 있습니다.

애니메이션 값 결합

addition, multiplication, division, 또는 modulo를 통해 두 애니메이션 값을 결합하여 새로운 애니메이션 값을 만들 수 있습니다.

애니메이션 값이 계산을 위해 다른 애니메이션 값을 반전시켜야 하는 경우가 있습니다. 예를 들어 scale(2x --> 0.5x)를 뒤집는 것이 있습니다.

const a = new Animated.Value(1);
const b = Animated.divide(1, a);

Animated.spring(a, {
  toValue: 2
}).start();

Interpolation

각 속성은 먼저 interpolation을 통해 실행할 수 있습니다. 보간법은 입력 범위를 출력 범위에 매핑합니다. 일반적으로 선형 보간을 사용하지만 완화 기능도 지원합니다. 기본적으로 주어진 범위를 벗어나 곡선을 추정하지만 출력 값을 clamp하도록 할 수도 있습니다.

0-1 범위를 0-100 범위로 변환하기 위한 기본 매핑은 다음과 같습니다.

value.interpolate({
  inputRange: [0, 1],
  outputRange: [0, 100]
});

예를 들어 Animated.Value에 대해 생각해 볼 수 있습니다.값은 0에서 1까지이지만, 위치는 150px에서 0px까지, 불투명도는 0에서 1까지 애니메이션화합니다. 위의 예에서 다음과 같이 스타일을 수정하여 이 작업을 수행할 수 있습니다.

  style={{
    opacity: this.state.fadeAnim, // Binds directly
    transform: [{
      translateY: this.state.fadeAnim.interpolate({
        inputRange: [0, 1],
        outputRange: [150, 0]  // 0 : 150, 0.5 : 75, 1 : 0
      }),
    }],
  }}
  

interpolate()은 또한 여러 범위 세그먼트를 지원하므로 정지 영역 및 기타 유용한 트릭을 정의하는 데 유용합니다. 예를 들어 -300에서 negation 관계를 얻고 -100에서 0으로 이동한 다음 0에서 1로 백업한 다음 100에서 0으로 다시 내린 다음 그 이후의 모든 것에 대해 0으로 유지되는 데드존으로 다시 되돌리려면 다음을 수행할 수 있습니다.

value.interpolate({
  inputRange: [-300, -100, 0, 100, 101],
  outputRange: [300, 0, 1, 0, 0]
});

맵은 다음과 같습니다.

Input | Output
------|-------
  -400|    450
  -300|    300
  -200|    150
  -100|      0
   -50|    0.5
     0|      1
    50|    0.5
   100|      0
   101|      0
   200|      0

또한 interpolate() 은 문자열에 대한 매핑을 지원하므로 단위를 사용하여 값뿐만 아니라 색을 애니메이션화할 수 있습니다. 예를 들어, 회전을 애니메이션화하려는 경우 다음을 수행할 수 있습니다.

value.interpolate({
  inputRange: [0, 360],
  outputRange: ['0deg', '360deg']
});

interpate()는 또한 임의 완화 함수를 지원하며, 이들 중 다수는 완화 모듈에 이미 구현되어 있습니다. 또한 interpolate()에는 outputRange를 extrapolating하기 위한 구성 가능한 동작이 있습니다. extrapolat, extrapolateLeft 또는 extrapolateRight 옵션을 설정하여 extrapolation을 설정할 수 있습니다. 기본값은 확장이지만 클램프를 사용하여 출력 값이 outputRange를 초과하지 않도록 할 수 있습니다.

동적 값 추적

애니메이션 값은 애니메이션의 값을 일반 숫자 대신 다른 애니메이션 값으로 설정하여 다른 값을 추적할 수도 있습니다. 예를 들어 안드로이드의 메신저에서 사용하는 것과 같은 "chat header" 애니메이션은 다른 애니메이션 값에 spring()을 고정하거나, timing()을 0으로 설정하여 구현할 수 있다. interpolation으로 구성할 수도 있습니다.

Animated.spring(follower, { toValue: leader }).start();
Animated.timing(opacity, {
  toValue: pan.x.interpolate({
    inputRange: [0, 300],
    outputRange: [1, 0]
  })
}).start();

리더와 추종자의 애니메이션 가치는 Animated.ValueXY()을 사용하여 구현됩니다. ValueXY는 이동 또는 끌기와 같은 2D 상호 작용을 처리하는 편리한 방법입니다. Animated.Value 인스턴스를 포함된 기본 래퍼이며 호출하는 일부 도우미 함수로 인해 ValueXY는 대부분의 경우 Value에 대한 대체품으로 사용됩니다. 위의 예에서 x와 y 값을 모두 추적할 수 있습니다.

제스처 추적

이동 또는 스크롤과 같은 제스처 및 기타 이벤트는 Animated.event를 사용하여 애니메이션 값에 직접 매핑할 수 있습니다. 이 작업은 복잡한 이벤트 개체에서 값을 추출할 수 있도록 구조화된 맵 구문을 사용하여 수행됩니다. 첫 번째 수준은 여러 arg에 걸쳐 매핑을 허용하는 배열이며 해당 배열에는 중첩된 개체가 포함되어 있습니다.

예를 들어 수평 스크롤 제스처로 작업할 때 event.nativeEvent.contentOffset.x를 scrollX(Animated.Value)로 매핑하려면 다음을 수행합니다.

 onScroll={Animated.event(
   // scrollX = e.nativeEvent.contentOffset.x
   [{ nativeEvent: {
        contentOffset: {
          x: scrollX
        }
      }
    }]
 )}

PanResponder를 사용할 때 다음 코드를 사용하여 getureState.dx 및 getureState.dy에서 x 및 y 위치를 추출할 수 있습니다. 배열의 첫 번째 위치에 null을 사용합니다. PanResponder 핸들러에 전달된 두 번째 인수인 getureState에만 관심이 있기 때문입니다.

onPanResponderMove={Animated.event(
  [null, // ignore the native event
  // extract dx and dy from gestureState
  // like 'pan.x = gestureState.dx, pan.y = gestureState.dy'
  {dx: pan.x, dy: pan.y}
])}

현재 애니메이션 에 응답

애니메이션을 하는 동안 현재 값을 읽을 수 있는 명확한 방법이 없다는 것을 알 수 있습니다. 그 이유는 최적화로 인해 기본 런타임에만 값을 알 수 있기 때문입니다. 현재 값에 대응하여 JavaScript를 실행해야 하는 경우 다음 두 가지 방법이 있습니다.

  • spring.stopAnimation(콜백)은 애니메이션을 중지하고 최종 값으로 콜백을 호출합니다. 이 기능은 제스처 전환을 할 때 유용합니다.
  • spring.addListener(콜백)는 애니메이션이 실행되는 동안 콜백을 비동기적으로 호출하여 최근 값을 제공합니다. 이 기능은 상태 변경을 트리거하는 데 유용합니다. 예를 들어 사용자가 새 옵션을 가까이 끌 때 새 옵션으로 보블을 스냅하는 등 상태 변경이 60fps에서 실행해야 하는 패닝과 같은 연속 제스처에 비해 몇 프레임의 지연에 덜 민감하기 때문입니다.
    애니메이션은 일반 자바스크립트 이벤트 루프와 독립적으로 고성능 방식으로 애니메이션을 실행할 수 있도록 완전히 직렬화할 수 있도록 설계되었습니다. 이것은 API에 영향을 미치므로, 완전히 동기화된 시스템에 비해 무언가를 하는 것이 조금 더 어려워 보일 때 명심하시길 바랍니다. Animated.Value.addListene 중 일부를 해결하기 위한 방법으로 사용하지만, 향후 성능에 영향을 미칠 수 있으므로 사용하지 마세요

기본 드라이버 사용

Animated API는 직렬화할 수 있도록 설계되었습니다. 네이티브 드라이버를 사용하면 애니메이션을 시작하기 전에 네이티브로 애니메이션에 대한 모든 것을 전송하여 네이티브 코드가 모든 프레임의 브리지를 거치지 않고도 UI 스레드에서 애니메이션을 수행할 수 있습니다. 애니메이션이 시작되면 애니메이션에 영향을 주지 않고 JS 스레드를 차단할 수 있습니다.

일반 애니메이션에 네이티브 드라이버를 사용하는 것은 간단합니다. 시작할 때 애니메이션 구성에 useNativeDriver: true를 추가할 수 있습니다.

Animated.timing(this.state.animatedValue, {
  toValue: 1,
  duration: 500,
  useNativeDriver: true // <-- Add this
}).start();

애니메이션 값은 하나의 드라이버에만 호환되므로, 해당 값에 대해 애니메이션을 시작할 때 네이티브 드라이버를 사용하는 경우 해당 값의 모든 애니메이션도 네이티브 드라이버를 사용해야 합니다.

또한 기본 드라이버는 Animated.event와 함께 작동합니다. 이 기능은 기본 드라이버가 없는 것처럼 스크롤 위치를 따르는 애니메이션에 특히 유용합니다. 애니메이션은 React Native의 비동기 특성으로 인해 항상 제스처 뒤에 프레임을 실행합니다.

<Animated.ScrollView // <-- Use the Animated ScrollView wrapper
  scrollEventThrottle={1} // <-- Use 1 here to make sure no events are ever missed
  onScroll={Animated.event(
    [
      {
        nativeEvent: {
          contentOffset: { y: this.state.animatedValue }
        }
      }
    ],
    { useNativeDriver: true } // <-- Add this
  )}>
  {content}
</Animated.ScrollView>

RNTester 앱을 실행한 다음 Native Animated 예제를 로드하면 작동 중인 네이티브 드라이버를 볼 수 있습니다. 소스 코드를 검토하여 이러한 예제가 어떻게 생성되었는지도 확인할 수 있습니다.

주의하세요

현재 기본 드라이버에서 애니메이션으로 수행할 수 있는 모든 작업이 지원되는 것은 아닙니다. 주된 제한 사항은 레이아웃이 아닌 속성만 애니메이션화할 수 있다는 것입니다. 변환 및 불투명도와 같은 속성은 작동하지만 Flexbox 및 위치 속성은 작동하지 않습니다. Animated.event를 사용할 때는 직접 이벤트에서만 작동하며 버블 이벤트에서는 작동하지 않습니다. 즉, PanResponder에서는 작동하지 않지만 ScrollView#onScroll과 같은 항목에서는 작동합니다.

애니메이션이 실행 중일 때 VirtualizedList 구성 요소가 더 많은 행을 렌더링하지 못하도록 할 수 있습니다. 사용자가 목록을 스크롤하는 동안 장시간 또는 반복 애니메이션을 실행해야 하는 경우 애니메이션 구성에서 isInteraction: false를 사용하여 이 문제를 방지할 수 있습니다.

명심하세요

RotateY, RotateX 등과 같은 변환 스타일을 사용하는 동안 변환 스타일 원근법이 제대로 적용되도록 합니다. 현재 일부 애니메이션은 이 애니메이션이 없으면 안드로이드에서 렌더링되지 않을 수 있습니다. 아래 예제를 참조하십시오.

<Animated.View
  style={{
    transform: [
      { scale: this.state.scale },
      { rotateY: this.state.rotateY },
      { perspective: 1000 } // without this line this Animation will not render on Android while working fine on iOS
    ]
  }}
/>

LayoutAnimation API

LayoutAnimation을 사용하면 다음 렌더링/레이아웃 주기의 모든 보기에 사용할 애니메이션 만들기 및 업데이트를 전체적으로 구성할 수 있습니다. 이 기능은 특정 속성을 직접 애니메이션화하기 위해 측정하거나 계산할 필요 없이 Flexbox 레이아웃 업데이트를 수행하는 데 유용하며, 레이아웃 변경 사항이 상위 항목의 크기를 늘리고 다른 경우 필요한 행을 아래로 밀어내리는 "자세한" 확장과 같은 상위 항목에 영향을 줄 때 특히 유용합니다. 구성 요소 간의 명시적 조정을 통해 모든 구성 요소를 동기화합니다.

LayoutAnimation은 매우 강력하고 매우 유용할 수 있지만 애니메이션 및 다른 애니메이션 라이브러리보다 훨씬 적은 제어 기능을 제공하므로 원하는 작업을 수행할 수 없는 경우 다른 방법을 사용해야 할 수 있습니다.

Android에서 이 작업을 수행하려면 UIManager를 통해 다음 플래그를 설정해야 합니다.

UIManager.setLayoutAnimationEnabledExperimental &&
  UIManager.setLayoutAnimationEnabledExperimental(true);

추가 참고사항

requestAnimationFram

requestAnimationFrame은 익숙한 브라우저의 폴리필입니다. 함수를 유일한 인수로 받아들이고 다음 재도장 전에 해당 함수를 호출합니다. 모든 자바스크립트 기반 애니메이션 API의 기반이 되는 애니메이션의 필수 구성 요소입니다. 일반적으로 애니메이션 API에서 프레임 업데이트를 관리할 수 있으므로 직접 이름을 지정할 필요가 없습니다.

setNativeProps

직접 조작 섹션에서 언급한 대로 setNativeProps를 사용하면 구성 요소 계층을 설정하고 다시 렌더링할 필요 없이 네이티브 백업 구성 요소(복합 구성 요소와 달리 실제로 네이티브 뷰에 의해 지원되는 구성 요소)의 속성을 직접 수정할 수 있습니다.

이 방법은 리바운드 예에서 규모를 업데이트하는 데 사용할 수 있습니다. 업데이트 중인 구성 요소가 깊이 중첩되어 있고 shouldComponentUpdate로 최적화되지 않은 경우에 유용할 수 있습니다.

프레임 손실(초당 60프레임 미만)이 있는 애니메이션을 찾은 경우 setNativeProps 또는 shouldComponentUpdate를 사용하여 최적화하십시오. 또는 useNativeDriver 옵션을 사용하여 자바스크립트 스레드가 아닌 UI 스레드에서 애니메이션을 실행할 수도 있습니다. 또한 InteractionManager를 사용하여 애니메이션이 완료될 때까지 계산 집약적인 작업을 연기할 수도 있습니다. 인앱 개발자 메뉴 "FPS 모니터" 도구를 사용하여 프레임률을 모니터링할 수 있습니다.

0개의 댓글