(Android/UI) Android 애니메이션 — ObjectAnimator, ValueAnimator


1. 속성 애니메이션이란?

값을 시간에 따라 변화시키고, 그 값을 객체의 속성에 적용하는 방식입니다. (구형 View Animation과 달리 실제 속성이 바뀌므로 터치 영역도 함께 이동합니다.)

ValueAnimator    : 값만 계산해 알려줌 (적용은 직접)
ObjectAnimator   : 값을 계산하고 객체의 속성(setter)에 자동 적용

2. ValueAnimator — 값 변화 직접 처리

ValueAnimator.ofFloat(0f, 1f).apply {
    duration = 600
    interpolator = DecelerateInterpolator()
    addUpdateListener { animator ->
        val value = animator.animatedValue as Float
        gaugeView.progress = value                     // 값을 원하는 곳에 직접 적용
    }
    start()
}

// 숫자 카운트업
ValueAnimator.ofInt(0, 12_500).apply {
    duration = 1000
    addUpdateListener { textView.text = "%,d".format(it.animatedValue as Int) }
    start()
}

// 색상 전환
ValueAnimator.ofArgb(Color.RED, Color.BLUE).apply {
    duration = 500
    addUpdateListener { view.setBackgroundColor(it.animatedValue as Int) }
    start()
}

3. ObjectAnimator — 속성에 바로 적용

setXxx 메서드가 있는 속성 이름을 문자열로 지정합니다.

ObjectAnimator.ofFloat(view, "translationX", 0f, 300f).apply {
    duration = 400
    start()
}

// View의 기본 속성
// translationX/Y, rotation, scaleX/Y, alpha 등
ObjectAnimator.ofFloat(view, View.ALPHA, 1f, 0f).start()          // 문자열 대신 상수 (오타 방지)
ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).start()

커스텀 속성을 애니메이션하려면 대상 객체에 setProgress(Float) 같은 setter가 있어야 합니다. Kotlin의 var progress는 setter가 생성되므로 "progress"로 사용할 수 있습니다.


4. ViewPropertyAnimator — 가장 간단한 방법

View의 흔한 변환은 animate()로 체이닝합니다. 여러 속성이 하나의 애니메이터로 최적화됩니다.

view.animate()
    .translationY(-200f)
    .alpha(0.5f)
    .scaleX(1.2f).scaleY(1.2f)
    .setDuration(300)
    .setInterpolator(OvershootInterpolator())
    .withEndAction { view.isVisible = false }         // 종료 시 실행
    .start()

단순 이동/페이드/확대는 animate()가 가장 간결하고 효율적입니다.


5. Interpolator — 속도 곡선

Interpolator 느낌
LinearInterpolator 일정한 속도
AccelerateInterpolator 점점 빨라짐
DecelerateInterpolator 점점 느려짐 (자연스러운 정지)
AccelerateDecelerateInterpolator 천천히 → 빠르게 → 천천히 (기본값)
OvershootInterpolator 목표를 살짝 넘었다 돌아옴
BounceInterpolator 튕김
FastOutSlowInInterpolator Material 표준

화면에 들어올 때는 Decelerate, 나갈 때는 Accelerate를 쓰면 자연스럽습니다.


6. AnimatorSet — 여러 애니메이션 조합

val fadeIn = ObjectAnimator.ofFloat(card, View.ALPHA, 0f, 1f)
val slideUp = ObjectAnimator.ofFloat(card, View.TRANSLATION_Y, 100f, 0f)
val scale = ObjectAnimator.ofFloat(icon, View.SCALE_X, 0.8f, 1f)

AnimatorSet().apply {
    playTogether(fadeIn, slideUp)             // 동시에
    play(scale).after(300)                    // 300ms 뒤에 이어서
    duration = 400
    start()
}

// 순차 실행
AnimatorSet().apply { playSequentially(anim1, anim2, anim3); start() }

7. 반복과 리스너

ObjectAnimator.ofFloat(heart, View.SCALE_X, 1f, 1.3f).apply {
    duration = 500
    repeatCount = ValueAnimator.INFINITE
    repeatMode = ValueAnimator.REVERSE         // 갔다가 되돌아오기
    start()
}

animator.addListener(object : AnimatorListenerAdapter() {
    override fun onAnimationEnd(animation: Animator) { /* 종료 */ }
    override fun onAnimationCancel(animation: Animator) { /* 취소 */ }
})

8. 스프링 애니메이션 (물리 기반)

// androidx.dynamicanimation:dynamicanimation-ktx
SpringAnimation(view, DynamicAnimation.TRANSLATION_Y, 0f).apply {
    spring.stiffness = SpringForce.STIFFNESS_LOW       // 강성
    spring.dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
    start()
}

시간 대신 물리 법칙으로 움직여 드래그 후 되돌아오는 동작처럼 사용자 입력에 자연스럽게 이어집니다.


9. 주의사항 — 생명주기

override fun onDestroyView() {
    animator?.cancel()                // 무한 반복/긴 애니메이션은 반드시 정리
    super.onDestroyView()
}

10. 정리



Related Posts