[Jetpack Compose] TargetBasedAnimation

유민국·2024년 10월 18일

같이보기
interface Animation
Animatable

TargetBasedAnimation

TargetBasedAnimation는 미리 정의된 종료값을 가진 모든 타겟 기반 래퍼 클래스이다
즉, 종료값이 사전에 정의된 애니메이션으로 DecayAnimation(감쇠)과 차이가 있다

TargetBasedAnimation는 시작 값과 속도, 종료 값이 애니메이션이 진행되는 동안 변경되지 않는 다고 가정하며, 이러한 값들을 캐시한다.

이러한 캐싱덕분에 애니메이션 값과 속도를 편리하게 조회할 수 있으며, 각 메서드에 단순히 시간만 전달하면 해당 시간에 따른 값을 얻을 수 있다

📌 타겟 기반 애니메이션이 중단될 경우, 현재 값과 속도를 시작 조건으로 사용하는 새로운 객체를 생성해야 한다.
이러한 중단 처리 방식은 AnimatableTransition에서의 기본 동작이기 때문에 이 두가지 API를 사용하면 복잡한 작업을 쉽게 처리할 수 있다.

.
.
.
.
기본적으로 Animation 인터페이스를 override하여 타겟 기반 애니메이션을 만들기 때문에 아래의 코드를 확인하기 보다 Animation 인터페이스가 어떤 기능인지 확인하는게 좋을 것 같다.

코드

fun <T, V : AnimationVector> TargetBasedAnimation(
    animationSpec: AnimationSpec<T>,
    typeConverter: TwoWayConverter<T, V>,
    initialValue: T,
    targetValue: T,
    initialVelocity: T
) = TargetBasedAnimation(
    animationSpec,
    typeConverter,
    initialValue,
    targetValue,
    typeConverter.convertToVector(initialVelocity)
)

class TargetBasedAnimation<T, V : AnimationVector> 
internal constructor(
    internal val animationSpec: VectorizedAnimationSpec<V>,
    override val typeConverter: TwoWayConverter<T, V>,
    initialValue: T,
    targetValue: T,
    initialVelocityVector: V? = null
) : Animation<T, V> {
    internal var mutableTargetValue: T = targetValue
        set(value) {
            if (field != value) {
                field = value
                targetValueVector = typeConverter
                .convertToVector(value)
                _endVelocity = null
                _durationNanos = -1L
            }
        }

    internal var mutableInitialValue: T = initialValue
        set(value) {
            if (value != field) {
                field = value
                initialValueVector = typeConverter
                .convertToVector(value)
                _endVelocity = null
                _durationNanos = -1L
            }
        }

    val initialValue: T
        get() = mutableInitialValue

    override val targetValue: T
        get() = mutableTargetValue

    // 주어진 시작/종료 값과 제공된 animationSpec으로 생성
    constructor(
        animationSpec: AnimationSpec<T>,
        typeConverter: TwoWayConverter<T, V>,
        initialValue: T,
        targetValue: T,
        initialVelocityVector: V? = null
    ) : this(
        animationSpec.vectorize(typeConverter),
        typeConverter,
        initialValue,
        targetValue,
        initialVelocityVector
    )

    private var initialValueVector =
    typeConverter.convertToVector(initialValue)
    private var targetValueVector = 
    typeConverter.convertToVector(targetValue)
    private val initialVelocityVector =
        initialVelocityVector?.copy() ?: 
        typeConverter.convertToVector(initialValue).newInstance()

    override val isInfinite: Boolean get() = animationSpec.isInfinite
    override fun getValueFromNanos(playTimeNanos: Long): T {
        return if (!isFinishedFromNanos(playTimeNanos)) {
            animationSpec.getValueFromNanos(
                playTimeNanos, initialValueVector,
                targetValueVector, initialVelocityVector
            ).let {
                for (i in 0 until it.size) {
                    checkPrecondition(!it.get(i).isNaN()) {
                        "AnimationVector cannot contain a NaN. $it."
                        + "Animation: $this," +
                            " playTimeNanos: $playTimeNanos"
                    }
                }
                typeConverter.convertFromVector(it)
            }
        } else {
            targetValue
        }
    }

    private var _durationNanos: Long = -1L

    @get:Suppress("MethodNameUnits")
    override val durationNanos: Long
        get() {
            if (_durationNanos < 0L) {
                _durationNanos = animationSpec.getDurationNanos(
                    initialValue = initialValueVector,
                    targetValue = targetValueVector,
                    initialVelocity = this.initialVelocityVector
                )
            }
            return _durationNanos
        }

    private var _endVelocity: V? = null

    private val endVelocity
        get() = _endVelocity ?: animationSpec.getEndVelocity(
                initialValueVector,
                targetValueVector,
                this.initialVelocityVector
            ).also { _endVelocity = it }

    override fun getVelocityVectorFromNanos(playTimeNanos: Long): V {
        return if (!isFinishedFromNanos(playTimeNanos)) {
            animationSpec.getVelocityFromNanos(
                playTimeNanos,
                initialValueVector,
                targetValueVector,
                initialVelocityVector
            )
        } else {
            endVelocity
        }
    }

    override fun toString(): String {
        return "TargetBasedAnimation: 
        "$initialValue -> $targetValue," +
            "initial velocity: $initialVelocityVector" + 
            "duration: $durationMillis ms," +
            "animationSpec: $animationSpec"
    }
}
profile
안녕하세요 😊

0개의 댓글