속성 애니메이션 개요

속성 애니메이션 시스템은 거의 모든 항목을 애니메이션으로 만들 수 있는 강력한 프레임워크입니다. 애니메이션을 정의하여 화면에 그리는지에 관계없이 시간 경과에 따라 객체 속성을 변경할 수 있습니다. 속성 애니메이션을 통해 지정된 시간 동안 속성(객체의 필드) 값을 변경합니다. 특정 항목을 애니메이션으로 보여주려면 애니메이션화할 객체 속성(예: 화면에서 객체의 위치, 애니메이션으로 보여줄 기간, 애니메이션으로 보여줄 값)을 지정합니다.

속성 애니메이션 시스템을 사용하면 애니메이션의 다음 특성을 정의할 수 있습니다.

  • 재생 시간: 애니메이션 재생 시간을 지정할 수 있습니다. 기본 길이는 300ms입니다.
  • 시간 보간: 속성의 값이 애니메이션의 현재 경과 시간 함수로 계산되는 방식을 지정할 수 있습니다.
  • 반복 횟수 및 동작: 재생 시간이 끝나고 애니메이션 반복 횟수에 도달하면 애니메이션 반복 여부를 지정할 수 있습니다. 애니메이션을 역방향으로 재생할지도 지정할 수 있습니다. 역방향으로 설정하면 반복 재생 횟수에 도달할 때까지 애니메이션이 앞뒤로 반복 재생됩니다.
  • 애니메이터 조합: 애니메이션을 함께 또는 순차적으로 재생하거나 지정된 지연 시간 후에 재생하는 논리 조합으로 그룹화할 수 있습니다.
  • 프레임 새로고침 지연: 애니메이션 프레임의 새로 고침 빈도를 지정할 수 있습니다. 기본값은 10ms마다 새로고치도록 설정되지만, 애플리케이션에서 프레임을 새로고칠 수 있는 속도는 궁극적으로 시스템의 전반적인 사용량과 시스템에서 기본 타이머를 제공하는 속도에 따라 달라집니다.

속성 애니메이션의 전체 예를 보려면 GitHub의 CustomTransition 샘플에 있는 ChangeColor 클래스를 확인하세요.

속성 애니메이션 작동 방식

먼저 간단한 예를 사용하여 애니메이션 작동 방식을 살펴보겠습니다. 그림 1에서는 화면의 가로 위치를 나타내는 x 속성을 사용하여 애니메이션화된 가상의 객체를 보여줍니다. 애니메이션의 재생 시간은 40ms로 설정되고 이동 거리는 40픽셀입니다. 기본 프레임 새로고침 빈도인 10ms마다 객체가 10픽셀씩 가로로 이동합니다. 마지막 40ms에 애니메이션이 중지되고 객체가 가로 위치 40에서 종료됩니다. 이 예는 선형 보간을 사용하는 애니메이션입니다. 즉, 객체가 일정한 속도로 움직입니다.

그림 1. 선형 애니메이션의 예

비선형 보간을 사용하도록 애니메이션을 지정할 수도 있습니다. 그림 2에서는 애니메이션의 시작 부분에서 가속화되고 애니메이션의 끝에서 감속하는 가상의 객체를 보여줍니다. 객체는 여전히 40ms 동안 40픽셀 만큼 움직이지만 비선형입니다. 이 애니메이션은 시작 부분에서 중간 지점까지는 가속하고, 중간 지점부터 애니메이션의 끝까지는 감속합니다. 그림 2에 표시된 바와 같이 애니메이션의 시작과 끝에서 이동한 거리는 중간보다 짧습니다.

그림 2. 비선형 애니메이션의 예

속성 애니메이션 시스템의 중요한 구성요소를 통해 위에 설명된 것과 같은 애니메이션의 계산 방법을 자세히 살펴보겠습니다. 그림 3에서는 기본 클래스가 서로 작동하는 방법을 보여줍니다.

그림 3. 애니메이션 계산 방법

ValueAnimator 객체를 통해서는 애니메이션 실행 시간 및 애니메이션으로 표시되는 속성의 현재 값과 같은 애니메이션의 타이밍을 추적합니다.

ValueAnimator에서는 애니메이션 보간을 정의하는 TimeInterpolator와 애니메이션으로 보여주는 속성 값의 계산 방법을 정의하는 TypeEvaluator를 캡슐화합니다. 예를 들어 그림 2에서 사용된 TimeInterpolatorAccelerateDecelerateInterpolator이고 TypeEvaluatorIntEvaluator입니다.

애니메이션을 시작하려면 ValueAnimator를 만들고 애니메이션으로 보여줄 속성의 시작 및 끝 값과 함께 애니메이션 재생 시간을 지정합니다. start()를 호출하면 애니메이션이 시작됩니다. 전체 애니메이션 중에 ValueAnimator에서는 애니메이션 재생 시간과 경과 시간에 따라 0과 1 사이의 경과된 비율을 계산합니다. 경과된 비율은 애니메이션이 완료된 시간의 비율을 나타내며 0은 0%를 의미하고 1은 100%를 의미합니다. 예를 들어, 그림 1에서 전체 지속 시간이 t = 40ms이므로 t = 10ms에서 경과된 비율은 .25가 됩니다.

ValueAnimator에서 경과된 비율을 계산하면 현재 설정된 TimeInterpolator를 호출하여 보간된 비율을 계산합니다. 보간된 비율을 통해서는 설정된 시간 보간을 고려하는 새 비율에 경과된 비율을 매핑합니다. 예를 들어 그림 2에서는 애니메이션이 느리게 가속되므로 약 .15인 보간된 비율은 t = 10ms에서 경과된 비율인 .25보다 작습니다. 그림 1에서는 보간된 비율이 항상 경과된 비율과 같습니다.

보간된 비율을 계산할 때, ValueAnimator에서는 적절한 TypeEvaluator를 호출하여 보간된 비율, 애니메이션의 시작 값 및 종료 값에 따라 애니메이션으로 보여줄 속성의 값을 계산합니다. 예를 들어 그림 2에서 보간된 비율은 t = 10ms에서 .15이므로 이 시점의 속성 값은 .15 × (40 - 0) 또는 6이 됩니다.

속성 애니메이션과 보기 애니메이션의 차이점

보기 애니메이션 시스템에서는 View 객체만 애니메이션으로 보여주는 기능을 제공하므로, 이(가) 아닌 객체를 애니메이션으로 보여주려면 자체 코드를 구현해야 합니다. 보기 애니메이션 시스템은 애니메이션으로 보여줄 의 몇 가지 요소(예: 배경색이 아니라 보기의 조정 및 회전)만 노출한다는 점에서 제한되어 있습니다.

보기 애니메이션의 또 다른 단점은 실제 보기 자체가 아니라 보기를 그린 위치만 수정한다는 것입니다. 예를 들어 화면 전체에서 이동하는 버튼을 애니메이션화하면 버튼은 제대로 그려지지만, 버튼을 클릭할 수 있는 실제 위치는 변경되지 않으므로, 이 문제를 처리할 고유 로직을 구현해야 합니다.

속성 애니메이션 시스템에서는 이러한 제약 조건이 완전히 삭제되므로 모든 객체(보기 및 보기가 아닌 객체)의 속성을 애니메이션으로 보여줄 수 있고 객체 자체가 실제로 수정됩니다. 속성 애니메이션 시스템은 애니메이션을 수행하는 방식에서도 더 강력합니다. 상위 수준에서 애니메이션으로 보여줄 색상, 위치 또는 크기 등의 속성에 애니메이터를 할당하고 여러 애니메이터의 보간 및 동기화와 같은 애니메이션 요소를 정의할 수 있습니다.

그러나 보기 애니메이션 시스템은 설정하는 데 시간이 덜 소요되고 작성해야 할 코드가 적습니다. 보기 애니메이션에서 필요한 모든 작업을 수행하거나 기존 코드가 이미 원하는 대로 작동하는 경우 속성 애니메이션 시스템을 사용할 필요가 없습니다. 사용 사례가 생기면 상황에 따라 두 애니메이션 시스템을 모두 사용할 수도 있습니다.

API 개요

속성 애니메이션 시스템의 API 중 대부분은 android.animation에서 찾을 수 있습니다. 보기 애니메이션 시스템에서는 이미 android.view.animation에 많은 보간을 정의하므로 속성 애니메이션 시스템에서도 이러한 보간을 사용할 수 있습니다. 다음 표에서는 속성 애니메이션 시스템의 주요 구성요소를 설명합니다.

Animator 클래스에서는 애니메이션을 만드는 데 사용할 기본 구조를 제공합니다. 이 클래스에서는 애니메이션 값을 완전히 지원하기 위해서는 확장해야 하는 최소한의 기능만 제공하므로, 일반적으로 이 클래스는 직접 사용하지 않습니다. 다음 서브클래스는 Animator를 확장합니다.

표 1. 애니메이터

클래스 설명
ValueAnimator 속성 애니메이션의 주요 타이밍 엔진으로, 애니메이션으로 보여줄 속성의 값도 계산합니다. 애니메이션 값을 계산하고 각 애니메이션의 타이밍 세부정보, 애니메이션 반복 여부, 업데이트 이벤트를 수신하는 리스너 및 평가할 맞춤 유형을 설정하는 기능을 포함하는 모든 핵심 기능을 포함합니다. 속성을 애니메이션화하는 데는 두 가지 요소가 있습니다. 즉, 애니메이션화된 값을 계산하고 애니메이션으로 보여줄 객체 및 속성에서 이 값을 설정합니다. ValueAnimator에서는 두 번째 요소를 수행하지 않으므로, ValueAnimator에서 계산한 값의 업데이트를 수신 대기하고 애니메이션으로 보여줄 객체를 고유 로직으로 수정해야 합니다. 자세한 내용은 ValueAnimator를 사용하여 애니메이션화 섹션을 참조하세요.
ObjectAnimator 애니메이션으로 보여줄 타겟 객체 및 객체 속성을 설정하는 데 사용할 수 있는 ValueAnimator의 서브클래스입니다. 이 클래스에서는 애니메이션의 새 값을 계산할 때 적절하게 속성을 업데이트합니다. ObjectAnimator를 사용하면 타겟 객체에서 값을 애니메이션하는 프로세스가 훨씬 쉬워지므로 대체로 이 클래스를 사용하고자 하합니다. 그러나 ObjectAnimator에는 타겟 객체에 특정 접근자 메서드가 있어야 하는 등의 몇 가지 추가 제한사항이 있으므로 직접ValueAnimator를 사용하려는 경우가 있습니다.
AnimatorSet 애니메이션이 서로 관련되어 실행되도록 함께 그룹화하는 메커니즘을 제공합니다. 애니메이션을 함께 또는 순차적으로 재생하거나 지정된 지연 시간 후에 재생하도록 설정할 수 있습니다. 자세한 내용은 애니메이터 조합으로 여러 애니메이션 구성 섹션을 참조하세요.

평가자가 속성 애니메이션 시스템에 지정된 속성의 값을 계산하는 방법을 알려줍니다. Animator 클래스에서 제공하는 타이밍 데이터와 애니메이션의 시작 및 종료 값을 사용하고 이 데이터를 기반으로 속성의 애니메이션 값을 계산합니다. 속성 애니메이션 시스템에서는 다음 평가자를 제공합니다.

표 2. 평가자

클래스/인터페이스 설명
IntEvaluator int 속성의 값을 계산하는 기본 평가자입니다.
FloatEvaluator float 속성의 값을 계산하는 기본 평가자입니다.
ArgbEvaluator 16진수 값으로 표시되는 색상 속성 값을 계산하는 기본 평가자입니다.
TypeEvaluator 고유 평가자를 생성하는 데 사용할 수 있는 인터페이스입니다. int, float 또는 색상이 아닌 객체 속성을 애니메이션화하면 객체 속성의 애니메이션 값을 계산하는 방법을 지정하는 TypeEvaluator 인터페이스를 구현해야 합니다. 기본 동작과 다르게 유형을 처리하려면 int, float 및 색상 값의 맞춤 TypeEvaluator도 지정할 수 있습니다. 맞춤 평가자 작성 방법에 관한 자세한 내용은 TypeEvaluator 사용 섹션을 참조하십시오.

시간 보간을 통해 애니메이션의 특정 값이 시간의 함수로 계산되는 방식을 정의합니다. 예를 들어 전체 애니메이션에서 선형으로 발생하도록 애니메이션을 지정하여 애니메이션이 전체 시간 동안 균일하게 이동하게 하거나, 애니메이션이 비선형 시간을 사용하도록 지정할 수 있습니다. 예를 들어 애니메이션의 시작 부분에서는 가속하고 끝에서는 감속합니다. 표 3에서는 android.view.animation에 포함된 보간을 설명합니다. 제공된 보간 중 요구사항에 맞는 것이 없으면 TimeInterpolator 인터페이스를 구현하거나 고유 보간을 만듭니다. 맞춤 보간을 작성하는 방법에 관한 자세한 내용은 보간 사용을 참조하세요.

표 3. 보간

클래스/인터페이스 설명
AccelerateDecelerateInterpolator 변경 속도가 처음과 끝에서는 느리지만 중간에는 가속되는 보간입니다.
AccelerateInterpolator 변경 속도가 처음에는 느리다가 가속되는 보간입니다.
AnticipateInterpolator 변경이 역방향으로 시작된 다음 신속하게 정방향으로 진행되는 보간입니다.
AnticipateOvershootInterpolator 변경이 역방향으로 시작한 다음 신속하게 정방향으로 진행되어 타겟 값을 넘어간 후, 다시 최종 값으로 돌아옵니다.
BounceInterpolator 변경이 끝에서 앞뒤로 왔다갔다하는 보간입니다.
CycleInterpolator 애니메이션이 지정된 주기 동안 반복되는 보간입니다.
DecelerateInterpolator 변경 속도가 처음에는 빠르다가 감속되는 보간입니다.
LinearInterpolator 변경 속도가 일정한 보간입니다.
OvershootInterpolator 정방향으로 신속하게 이동하여 마지막 값을 넘어선 다음 되돌아오는 보간입니다.
TimeInterpolator 고유 보간을 구현하는 데 사용할 수 있는 인터페이스입니다.

ValueAnimator를 사용하여 애니메이션화

ValueAnimator 클래스를 사용하면 애니메이션화할 int, float 또는 색상 값 조합을 지정하여 애니메이션 기간의 유형 값을 애니메이션화할 수 있습니다. ofInt(), ofFloat() 또는 ofObject()의 팩토리 메서드 중 하나를 호출하여 ValueAnimator를 얻습니다. 예:

Kotlin

    ValueAnimator.ofFloat(0f, 100f).apply {
        duration = 1000
        start()
    }
    

Java

    ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
    animation.setDuration(1000);
    animation.start();
    

이 코드에서 ValueAnimatorstart() 메서드를 실행할 때 1000ms 기간 동안 0에서 100 사이의 애니메이션 값을 계산하기 시작합니다.

다음을 수행하여 애니메이션으로 보여줄 맞춤 유형도 지정할 수 있습니다.

Kotlin

    ValueAnimator.ofObject(MyTypeEvaluator(), startPropertyValue, endPropertyValue).apply {
        duration = 1000
        start()
    }
    

Java

    ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
    animation.setDuration(1000);
    animation.start();
    

이 코드에서 ValueAnimatorstart() 메서드가 실행될 때 1000ms의 기간 동안 MyTypeEvaluator에서 제공한 로직을 사용하여 startPropertyValue ~ endPropertyValue의 애니메이션 값을 계산하기 시작합니다.

다음 코드에 표시된 대로 AnimatorUpdateListenerValueAnimator 객체에 추가하여 애니메이션 값을 사용할 수 있습니다.

Kotlin

    ValueAnimator.ofObject(...).apply {
        ...
        addUpdateListener { updatedAnimation ->
            // You can use the animated value in a property that uses the
            // same type as the animation. In this case, you can use the
            // float value in the translationX property.
            textView.translationX = updatedAnimation.animatedValue as Float
        }
        ...
    }
    

Java

    animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator updatedAnimation) {
            // You can use the animated value in a property that uses the
            // same type as the animation. In this case, you can use the
            // float value in the translationX property.
            float animatedValue = (float)updatedAnimation.getAnimatedValue();
            textView.setTranslationX(animatedValue);
        }
    });
    

onAnimationUpdate() 메서드에서 업데이트된 애니메이션 값에 액세스한 다음 보기 중 하나의 속성에 사용할 수 있습니다. 리스너에 관한 자세한 내용은 애니메이션 리스너 섹션을 참조하세요.

ObjectAnimator를 사용하여 애니메이션화

ObjectAnimatorValueAnimator(이전 섹션에서 설명함)의 서브클래스이고 타이밍 엔진과 ValueAnimator의 값 계산을 조합하며 타겟 객체의 이름 지정된 속성을 애니메이션화하는 기능이 있습니다. 그러면 애니메이션 속성이 자동으로 업데이트되므로 더 이상 ValueAnimator.AnimatorUpdateListener를 구현할 필요가 없기 때문에 객체를 애니메이션으로 보여주기가 훨씬 쉬워집니다.

ObjectAnimator를 인스턴스화하는 작업은 ValueAnimator와 비슷하지만 애니메이션할 값 범위와 함께 객체와 객체의 속성 이름(문자열)도 지정합니다.

Kotlin

    ObjectAnimator.ofFloat(textView, "translationX", 100f).apply {
        duration = 1000
        start()
    }
    

Java

    ObjectAnimator animation = ObjectAnimator.ofFloat(textView, "translationX", 100f);
    animation.setDuration(1000);
    animation.start();
    

올바른 ObjectAnimator 업데이트 속성이 있으려면 다음을 수행해야 합니다.

  • 애니메이션화할 객체 속성에는 set<PropertyName>() 양식의 setter 함수(카멜 대/소문자)가 있어야 합니다. 애니메이션 중에 ObjectAnimator에서 속성을 자동으로 업데이트하므로, 이 setter 메서드를 사용하여 속성에 액세스할 수 있어야 합니다. 예를 들어 속성 이름이 foo이면 setFoo() 메서드가 있어야 합니다. 이 setter 메서드가 없으면 다음 세 가지 옵션이 있습니다.
    • 적절한 권한이 있으면 클래스에 setter 메서드를 추가합니다.
    • 변경할 권한이 있는 래퍼 클래스를 사용하고 이 래퍼에서 올바른 setter 메서드로 값을 받아 원본 객체에 전달하게 합니다.
    • 대신 ValueAnimator를 사용합니다.
  • ObjectAnimator 팩토리 메서드 중 하나에서 values... 매개변수의 값을 하나만 지정하면 이 값이 애니메이션의 종료 값으로 간주됩니다. 따라서 애니메이션화할 객체 속성에는 애니메이션의 시작 값을 가져오는 데 사용되는 getter 함수가 있어야 합니다. getter 함수는 get<PropertyName>() 양식이어야 합니다. 예를 들어 속성 이름이 foo이면 getFoo() 메서드가 있어야 합니다.
  • 애니메이션화할 속성의 getter(필요한 경우) 및 setter 메서드는 ObjectAnimator로 지정하는 시작 및 끝 값과 동일한 유형에서 작동해야 합니다. 예를 들어 다음 ObjectAnimator를 생성하면 targetObject.setPropName(float)targetObject.getPropName(float)이(가) 있어야 합니다.
        ObjectAnimator.ofFloat(targetObject, "propName", 1f)
        
  • 애니메이션화할 속성 또는 객체에 따라, 화면에서 업데이트된 애니메이션 값으로 다시 그리도록 보기에서 invalidate() 메서드를 호출해야 할 수도 있습니다. 이 작업은 onAnimationUpdate() 콜백에서 실행합니다. 예를 들어 Drawable 객체의 색상 속성을 애니메이션화하면 객체를 다시 그릴 때만 화면이 업데이트됩니다. setAlpha()setTranslationX()와 같은 View의 모든 속성 setter는 View를 적절하게 무효화하므로, 새로운 값을 사용하여 이 메서드를 호출할 때 View를 무효화할 필요가 없습니다. 리스너에 관한 자세한 내용은 애니메이션 리스너 섹션을 참조하세요.

AnimatorSet를 사용하여 여러 애니메이션 구성

대부분의 경우 다른 애니메이션이 시작되거나 완료되는 시점에 따라 달라지는 애니메이션을 재생합니다. Android 시스템을 사용하면 애니메이션을 AnimatorSet로 함께 번들하여, 애니메이션을 동시 또는 순차적으로 시작할지 아니면 지정된 지연 시간 후에 시작할지를 지정할 수 있습니다. AnimatorSet 객체를 서로 중첩시킬 수도 있습니다.

다음 코드 스니펫은 다음 Animator 객체를 다음과 같은 방식으로 재생합니다.

  1. bounceAnim를 재생합니다.
  2. squashAnim1, squashAnim2, stretchAnim1stretchAnim2를 동시에 재생합니다.
  3. bounceBackAnim를 재생합니다.
  4. fadeAnim를 재생합니다.

Kotlin

    val bouncer = AnimatorSet().apply {
        play(bounceAnim).before(squashAnim1)
        play(squashAnim1).with(squashAnim2)
        play(squashAnim1).with(stretchAnim1)
        play(squashAnim1).with(stretchAnim2)
        play(bounceBackAnim).after(stretchAnim2)
    }
    val fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply {
        duration = 250
    }
    AnimatorSet().apply {
        play(bouncer).before(fadeAnim)
        start()
    }
    

Java

    AnimatorSet bouncer = new AnimatorSet();
    bouncer.play(bounceAnim).before(squashAnim1);
    bouncer.play(squashAnim1).with(squashAnim2);
    bouncer.play(squashAnim1).with(stretchAnim1);
    bouncer.play(squashAnim1).with(stretchAnim2);
    bouncer.play(bounceBackAnim).after(stretchAnim2);
    ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
    fadeAnim.setDuration(250);
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.play(bouncer).before(fadeAnim);
    animatorSet.start();
    

애니메이션 리스너

아래에 설명된 리스너를 사용하여 애니메이션 기간 동안 중요한 이벤트를 수신 대기할 수 있습니다.

  • Animator.AnimatorListener
  • ValueAnimator.AnimatorUpdateListener
    • onAnimationUpdate() - 애니메이션의 모든 프레임에서 호출됩니다. 애니메이션 중에 ValueAnimator에서 생성하는 계산된 값을 사용하기 위해 이 이벤트를 수신 대기합니다. 값을 사용하려면 이벤트에 전달된 ValueAnimator 객체를 쿼리하여 getAnimatedValue() 메서드를 통해 애니메이션된 현재 값을 가져옵니다. ValueAnimator를 사용하면 이 리스너를 구현해야 합니다.

      애니메이션화할 속성이나 객체에 따라 View에서 invalidate()를 호출하여 애니메이션화된 새로운 값으로 화면의 영역을 다시 그려야 할 수도 있습니다. 예를 들어 Drawable 객체의 색상 속성을 애니메이션화하면 객체를 다시 그릴 때만 화면이 업데이트됩니다. setAlpha()setTranslationX()와 같은 보기의 모든 속성 setter는 View를 적절하게 무효화하므로, 새로운 값을 사용하여 이 메서드를 호출할 때 View를 무효화할 필요가 없습니다.

Animator.AnimatorListener 인터페이스의 모든 메서드를 구현하지 않아도 되면 인터페이스를 구현하는 대신 AnimatorListenerAdapter 클래스를 확장할 수 있습니다. AnimatorListenerAdapter 클래스에서는 재정의하도록 선택할 수 있는 메서드의 빈 구현을 제공합니다.

예를 들어 다음 코드 스니펫에서는 onAnimationEnd() 콜백용으로만 AnimatorListenerAdapter를 만듭니다.

Kotlin

    ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply {
        duration = 250
        addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) {
                balls.remove((animation as ObjectAnimator).target)
            }
        })
    }
    

Java

    ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
    fadeAnim.setDuration(250);
    fadeAnim.addListener(new AnimatorListenerAdapter() {
    public void onAnimationEnd(Animator animation) {
        balls.remove(((ObjectAnimator)animation).getTarget());
    }
    

ViewGroup 객체의 레이아웃 변경사항 애니메이션화

속성 애니메이션 시스템에서는 ViewGroup 객체의 변경사항을 애니메이션화하는 기능을 제공하고 View 객체 자체를 애니메이션화하는 쉬운 방법도 제공합니다.

LayoutTransition 클래스를 사용하여 ViewGroup의 레이아웃 변경사항을 애니메이션으로 보여줄 수 있습니다. ViewGroup의 View는 ViewGroup에 추가하거나 삭제할 때 또는 VISIBLE, INVISIBLE 또는 GONE를 사용하여 View의 setVisibility() 메서드를 호출할 때 애니메이션이 표시되고 사라집니다. View를 추가하거나 삭제할 때 ViewGroup의 나머지 View도 새 위치로 애니메이션될 수 있습니다. setAnimator()를 호출하고 다음 LayoutTransition 상수 중 하나와 함께 Animator 객체로 전달하여 LayoutTransition 객체에 다음 애니메이션을 정의할 수 있습니다.

  • APPEARING - 컨테이너에 나타나는 항목에서 실행되는 애니메이션을 나타내는 플래그입니다.
  • CHANGE_APPEARING - 컨테이너에 표시되는 새 항목 때문에 변경된 항목에서 실행되는 애니메이션을 나타내는 플래그입니다.
  • DISAPPEARING - 컨테이너에서 사라지는 항목에서 실행되는 애니메이션을 나타내는 플래그입니다.
  • CHANGE_DISAPPEARING - 컨테이너에서 항목이 사라지기 때문에 변경되는 항목에서 실행되는 애니메이션을 나타내는 플래그입니다.

이러한 4가지 유형의 이벤트에 맞는 고유 맞춤 애니메이션을 정의하여 레이아웃 전환 모양을 맞춤 설정하거나 애니메이션 시스템에서 기본 애니메이션을 사용하도록 지정할 수 있습니다.

API 데모의 LayoutAnimations 샘플에서는 레이아웃 전환의 애니메이션을 정의한 다음 애니메이션화할 View 객체에서 애니메이션을 설정하는 방법을 보여줍니다.

LayoutAnimationsByDefault 및 대응하는 layout_animations_by_default.xml 레이아웃 리소스 파일에서는 XML로 ViewGroups의 기본 레이아웃 전환을 사용으로 설정하는 방법을 보여줍니다. ViewGroup의 android:animateLayoutchanges 속성을 true로 설정하기만 하면 됩니다. 예:

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:id="@+id/verticalContainer"
        android:animateLayoutChanges="true" />
    

이 속성을 true로 설정하면 ViewGroup에 추가되거나 삭제된 View와 ViewGroup의 나머지 View가 자동으로 애니메이션됩니다.

StateListAnimator를 사용하여 보기 상태 변경 애니메이션화

StateListAnimator 클래스를 사용하면 보기 상태가 변경될 때 실행되는 애니메이터를 정의할 수 있습니다. 이 객체는 Animator 객체의 래퍼로 작동하여, 지정된 보기 상태(예 : '누름' 또는 '포커스됨')가 변경될 때마다 애니메이션을 호출합니다.

StateListAnimator는 각각 클래스에 따라 정의된 다양한 보기 상태를 정의하는 루트 <selector> 요소 및 하위 <item> 요소로 XML 리소스에 정의할 수 있습니다. 각 <item>에는 속성 애니메이션 조합의 정의가 포함되어 있습니다.

예를 들어, 다음 파일에서는 보기를 눌렀을 때 보기의 x 및 y 눈금을 변경하는 상태 목록 애니메이터를 만듭니다.

res/xml/animate_scale.xml

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- the pressed state; increase x and y size to 150% -->
        <item android:state_pressed="true">
            <set>
                <objectAnimator android:propertyName="scaleX"
                    android:duration="@android:integer/config_shortAnimTime"
                    android:valueTo="1.5"
                    android:valueType="floatType"/>
                <objectAnimator android:propertyName="scaleY"
                    android:duration="@android:integer/config_shortAnimTime"
                    android:valueTo="1.5"
                    android:valueType="floatType"/>
            </set>
        </item>
        <!-- the default, non-pressed state; set x and y size to 100% -->
        <item android:state_pressed="false">
            <set>
                <objectAnimator android:propertyName="scaleX"
                    android:duration="@android:integer/config_shortAnimTime"
                    android:valueTo="1"
                    android:valueType="floatType"/>
                <objectAnimator android:propertyName="scaleY"
                    android:duration="@android:integer/config_shortAnimTime"
                    android:valueTo="1"
                    android:valueType="floatType"/>
            </set>
        </item>
    </selector>
    

상태 목록 애니메이터를 보기에 연결하려면 다음과 같이 android:stateListAnimator 속성을 추가합니다.

    <Button android:stateListAnimator="@xml/animate_scale"
            ... />
    

이제 이 버튼의 상태가 변경되면 animate_scale.xml에 정의된 애니메이션이 사용됩니다.

또는 대신 상태 목록 애니메이터를 코드의 보기에 할당하려면 AnimatorInflater.loadStateListAnimator() 메서드를 사용하고 View.setStateListAnimator() 메서드로 보기에 애니메이터를 할당합니다.

또는 보기의 속성을 애니메이션화하는 대신 AnimatedStateListDrawable를 사용하여 상태 변경 사이 드로어블 애니메이션을 재생할 수 있습니다. Android 5.0의 일부 시스템 위젯에서는 기본적으로 이 애니메이션을 사용합니다. 다음 예에서는 AnimatedStateListDrawable를 XML 리소스로 정의하는 방법을 보여줍니다.

    <!-- res/drawable/myanimstatedrawable.xml -->
    <animated-selector
        xmlns:android="http://schemas.android.com/apk/res/android">

        <!-- provide a different drawable for each state-->
        <item android:id="@+id/pressed" android:drawable="@drawable/drawableP"
            android:state_pressed="true"/>
        <item android:id="@+id/focused" android:drawable="@drawable/drawableF"
            android:state_focused="true"/>
        <item android:id="@id/default"
            android:drawable="@drawable/drawableD"/>

        <!-- specify a transition -->
        <transition android:fromId="@+id/default" android:toId="@+id/pressed">
            <animation-list>
                <item android:duration="15" android:drawable="@drawable/dt1"/>
                <item android:duration="15" android:drawable="@drawable/dt2"/>
                ...
            </animation-list>
        </transition>
        ...
    </animated-selector>
    

TypeEvaluator 사용

Android 시스템에서 알 수 없는 유형을 애니메이션화하려면 TypeEvaluator 인터페이스를 구현하여 고유 평가자를 만들 수 있습니다. Android 시스템에 알려진 유형은 IntEvaluator, FloatEvaluatorArgbEvaluator 유형 평가자에서 지원하는 int, float 또는 색상입니다.

TypeEvaluator 인터페이스에서 구현하는 유일한 메서드는 evaluate() 메서드입니다. 그러면 사용 중인 애니메이터에서 현재 애니메이션 시점에 애니메이션된 속성의 값을 반환할 수 있습니다. FloatEvaluator 클래스에서는 이 작업 방법을 보여줍니다.

Kotlin

    private class FloatEvaluator : TypeEvaluator<Any> {

        override fun evaluate(fraction: Float, startValue: Any, endValue: Any): Any {
            return (startValue as Number).toFloat().let { startFloat ->
                startFloat + fraction * ((endValue as Number).toFloat() - startFloat)
            }
        }

    }
    

Java

    public class FloatEvaluator implements TypeEvaluator {

        public Object evaluate(float fraction, Object startValue, Object endValue) {
            float startFloat = ((Number) startValue).floatValue();
            return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
        }
    }
    

메모: ValueAnimator(또는 ObjectAnimator)이(가) 실행될 때 애니메이션의 현재 경과된 비율(0에서 1 사이의 값)을 계산하고 사용 중인 보간에 따라 보간된 버전을 계산합니다. 보간된 비율은 TypeEvaluator이(가) fraction 매개변수를 통해 수신하므로, 애니메이션화된 값을 계산할 때 보간을 고려하지 않아도 됩니다.

보간 사용

보간을 통해서는 애니메이션의 특정 값이 시간 함수로 계산되는 방식을 정의합니다. 예를 들어 전체 애니메이션에서 선형으로 발생하도록 애니메이션을 지정하여 애니메이션이 전체 시간 동안 균일하게 이동하게 하거나, 애니메이션이 비선형 시간을 사용하도록 지정할 수 있습니다. 예를 들어 애니메이션의 시작 부분에서는 가속하고 끝에서는 감속합니다.

애니메이션 시스템의 보간은 애니메이션의 경과 시간을 나타내는 애니메이터에서 비율을 받습니다. 보간에서 제공하려는 애니메이션 유형에 맞게 이 비율을 수정합니다. Android 시스템에서는 android.view.animation package에서 일반 보간 조합을 제공합니다. 요구사항에 맞는 보간이 없으면 TimeInterpolator 인터페이스를 구현하고 고유 보간을 만들 수 있습니다.

예를 들어 기본 AccelerateDecelerateInterpolatorLinearInterpolator에서 보간된 비율을 계산하는 방법은 아래 비교되어 있습니다. LinearInterpolator는 경과된 비율에 영향을 주지 않습니다. AccelerateDecelerateInterpolator는 애니메이션으로 들어갈 때 가속하고 나올 때 감속합니다. 다음 메서드를 통해서는 이러한 보간의 로직을 정의합니다.

AccelerateDecelerateInterpolator

Kotlin

    override fun getInterpolation(input: Float): Float =
            (Math.cos((input + 1) * Math.PI) / 2.0f).toFloat() + 0.5f
    

Java

    @Override
    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }
    

LinearInterpolator

Kotlin

    override fun getInterpolation(input: Float): Float = input
    

Java

    @Override
    public float getInterpolation(float input) {
        return input;
    }
    

다음 표에서는 1000ms 간 지속되는 애니메이션의 보간을 통해 계산되는 대략적인 값을 나타냅니다.

ms 경과됨 경과된 비율/보간된 비율(선형) 보간된 비율(가속 또는 감속)
0 0 0
200 .2 .1
400 .4 .345
600 .6 .8
800 .8 .9
1000 1 1

표에 표시된 대로 LinearInterpolator에서는 200ms가 경과할 때마다 0.2의 동일한 속도로 값을 변경합니다. AccelerateDecelerateInterpolator는 200ms ~ 600ms에서는 LinearInterpolator보다 값을 빠르게 변경하고 600ms ~ 1000ms에서는 느리게 변경합니다.

키프레임 지정

Keyframe 객체는 애니메이션의 특정 시간에 특정 상태를 정의할 수 있는 시간/값 쌍으로 구성됩니다. 각 키프레임에는 이전 키 프레임의 시간과 이 키프레임 시간 사이의 간격 동안 애니메이션의 동작을 제어하는 자체 보간도 있을 수 있습니다.

Keyframe 객체를 인스턴스화하려면 팩토리 메서드, ofInt(), ofFloat() 또는 ofObject() 중 하나를 사용하여 적절한 유형의 를 얻어야 합니다. 그런 다음 ofKeyframe() 팩토리 메서드를 호출하여 PropertyValuesHolder 객체를 획득합니다. 객체가 있으면 PropertyValuesHolder 객체와 애니메이션화할 객체를 전달하여 애니메이터를 얻을 수 있습니다. 다음 코드 스니펫에서는 이 작업 방법을 보여줍니다.

Kotlin

    val kf0 = Keyframe.ofFloat(0f, 0f)
    val kf1 = Keyframe.ofFloat(.5f, 360f)
    val kf2 = Keyframe.ofFloat(1f, 0f)
    val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2)
    ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation).apply {
        duration = 5000
    }
    

Java

    Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
    Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
    Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
    PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
    ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation);
    rotationAnim.setDuration(5000);
    

보기 애니메이션

속성 애니메이션 시스템을 사용하면 보기 객체의 간소화된 애니메이션이 가능하며 보기 애니메이션 시스템에 비해 몇 가지 장점을 제공합니다. 보기 애니메이션 시스템에서는 그린 방식을 변경하여 View 객체를 변형합니다. View 자체에는 조작할 속성이 없으므로 이는 각 View의 컨테이너에서 처리되었습니다. 따라서 View는 애니메이션으로 보여주지만 View 객체 자체는 변경되지 않습니다. 그러면 객체를 화면의 다른 위치에 그려도 객체가 여전히 원본 위치에 있는 등의 방식으로 동작합니다. Android 3.0에서는 이러한 단점을 없애기 위해 새 속성과 해당하는 getter 및 setter 메서드가 추가되었습니다.

속성 애니메이션 시스템에서는 View 객체의 실제 속성을 변경하여 화면에서 View를 애니메이션으로 보여줄 수 있습니다. 또한 View에서 자동으로 invalidate() 메서드를 호출하여 속성을 변경할 때마다 화면을 새로 고침합니다. 속성 애니메이션을 용이하게 하는 View 클래스의 새 속성은 다음과 같습니다.

  • translationXtranslationY: 이 속성은 레이아웃 컨테이너에서 설정한 왼쪽 및 위쪽 좌표로부터의 델타 값으로 View의 위치를 제어합니다.
  • rotation, rotationXrotationY: 이 속성은 중심점을 기준으로 3D 및 2D( 속성)에서 회전을 제어합니다.
  • scaleXscaleY: 이 속성은 중심점을 기준으로 View의 2D 크기 조정을 제어합니다.
  • pivotXpivotY: 이 속성은 회전 및 크기 조정 변환이 발생하는 중심점의 위치를 제어합니다. 기본적으로 중심점은 객체의 가운데에 있습니다.
  • xy: 컨테이너에서 View의 최종 위치를 왼쪽 및 위쪽 값과 translationX 및 translationY 값의 합계로 나타내는 간단한 유틸리티 속성입니다.
  • alpha: View의 알파 투명도를 나타냅니다. 이 값은 기본적으로 1(불투명)이며 값 0은 완전 투명(표시되지 않음)을 나타냅니다.

색상이나 회전 값과 같은 View 객체의 속성을 애니메이션화하려면 속성 애니메이터를 만들고 애니메이션화할 View 속성만 지정하면 됩니다. 예:

Kotlin

    ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f)
    

Java

    ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
    

애니메이터 만들기에 관한 자세한 내용은 ValueAnimatorObjectAnimator를 사용하여 애니메이션화하는 데 관한 섹션을 참조하세요.

ViewPropertyAnimator를 사용하여 애니메이션화

ViewPropertyAnimator에서는 하나의 기본 Animator 객체를 사용하여 View의 여러 속성을 병렬로 애니메이션하는 간단한 방법을 제공합니다. 보기 속성의 실제 값을 수정하므로 ObjectAnimator와 매우 비슷하게 작동하지만, 여러 속성을 한 번에 애니메이션화할 때 더 효율적입니다. 또한 ViewPropertyAnimator를 사용하기 위한 코드는 훨씬 간결하므로 읽기가 더 쉽습니다. 다음 코드 스니펫에서는 보기의 xy 속성을 동시에 애니메이션화할 때 여러 ObjectAnimator 객체, 단일 ObjectAnimatorViewPropertyAnimator를 사용하는 차이점을 보여줍니다.

여러 ObjectAnimator 객체

Kotlin

    val animX = ObjectAnimator.ofFloat(myView, "x", 50f)
    val animY = ObjectAnimator.ofFloat(myView, "y", 100f)
    AnimatorSet().apply {
        playTogether(animX, animY)
        start()
    }
    

Java

    ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
    ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
    AnimatorSet animSetXY = new AnimatorSet();
    animSetXY.playTogether(animX, animY);
    animSetXY.start();
    

하나의 ObjectAnimator

Kotlin

    val pvhX = PropertyValuesHolder.ofFloat("x", 50f)
    val pvhY = PropertyValuesHolder.ofFloat("y", 100f)
    ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start()
    

Java

    PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
    PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
    ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start();
    

ViewPropertyAnimator

Kotlin

    myView.animate().x(50f).y(100f)
    

Java

    myView.animate().x(50f).y(100f);
    

ViewPropertyAnimator에 관한 자세한 내용은 Android 개발자 블로그 게시물을 참조하세요.

XML로 애니메이션 선언

속성 애니메이션 시스템에서는 프로그래밍 방식이 아니라 XML을 사용하여 속성 애니메이션을 선언할 수 있습니다. XML로 애니메이션을 정의하면 애니메이션을 여러 활동에 쉽게 재사용할 수 있고 애니메이션 시퀀스를 더욱 쉽게 수정할 수 있습니다.

새 속성 애니메이션 API를 사용하는 애니메이션 파일을 기존 보기 애니메이션 프레임워크를 사용하는 파일과 구분하려면 Android 3.1부터 속성 애니메이션의 XML 파일을 res/animator/ 디렉터리에 저장해야 합니다.

다음 속성 애니메이션 클래스에는 다음 XML 태그가 포함된 XML 선언 지원이 있습니다.

XML 선언에서 사용할 수 있는 속성을 찾으려면 애니메이션 리소스를 참조하세요. 다음 예제에서는 두 개의 객체 애니메이션 조합을 순차적으로 재생하고, 첫 번째 중첩된 조합은 두 개의 객체 애니메이션을 함께 재생합니다.

    <set android:ordering="sequentially">
        <set>
            <objectAnimator
                android:propertyName="x"
                android:duration="500"
                android:valueTo="400"
                android:valueType="intType"/>
            <objectAnimator
                android:propertyName="y"
                android:duration="500"
                android:valueTo="300"
                android:valueType="intType"/>
        </set>
        <objectAnimator
            android:propertyName="alpha"
            android:duration="500"
            android:valueTo="1f"/>
    </set>
    

이 애니메이션을 실행하려면 코드의 XML 리소스를 AnimatorSet 객체로 확장한 다음 애니메이션 조합을 시작하기 전에 모든 애니메이션의 타겟 객체를 설정해야 합니다. setTarget()를 호출하면 편의상 AnimatorSet의 모든 하위 요소의 단일 타겟 객체를 설정합니다. 다음 코드에서는 이 작업을 실행하는 방법을 보여줍니다.

Kotlin

    (AnimatorInflater.loadAnimator(myContext, R.animator.property_animator) as AnimatorSet).apply {
        setTarget(myObject)
        start()
    }
    

Java

    AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
        R.animator.property_animator);
    set.setTarget(myObject);
    set.start();
    

다음 예에 표시된 대로 XML로 ValueAnimator도 선언할 수 있습니다.

    <animator xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="1000"
        android:valueType="floatType"
        android:valueFrom="0f"
        android:valueTo="-100f" />
    

코드에서 이전 ValueAnimator를 사용하려면 객체를 확장하고 AnimatorUpdateListener를 추가한 다음, 업데이트된 애니메이션 값을 가져와서 다음 코드에 표시된 대로 보기 중 하나의 속성에 사용해야 합니다.

Kotlin

    (AnimatorInflater.loadAnimator(this, R.animator.animator) as ValueAnimator).apply {
        addUpdateListener { updatedAnimation ->
            textView.translationX = updatedAnimation.animatedValue as Float
        }

        start()
    }
    

Java

    ValueAnimator xmlAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this,
            R.animator.animator);
    xmlAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator updatedAnimation) {
            float animatedValue = (float)updatedAnimation.getAnimatedValue();
            textView.setTranslationX(animatedValue);
        }
    });

    xmlAnimator.start();
    

속성 애니메이션을 정의하는 XML 구문에 관한 자세한 내용은 애니메이션 리소스를 참조하세요.

UI 성능에 미치는 잠재적 영향

UI를 업데이트하는 애니메이터를 사용하면 애니메이션이 실행되는 모든 프레임에서 추가 렌더링 작업이 수행됩니다. 따라서 리소스를 많이 사용하는 애니메이션을 사용하면 앱 성능에 부정적인 영향을 줄 수 있습니다.

UI를 애니메이션화하는 데 필요한 작업이 렌더링 파이프라인의 애니메이션 단계에 추가됩니다. 프로필 GPU 렌더링을 사용으로 설정하고 애니메이션 단계를 모니터링하여 애니메이션이 앱의 성능에 영향을 미치는지 확인할 수 있습니다. 자세한 내용은 프로필 GPU 렌더링 둘러보기를 참조하세요.