TargetBasedAnimation는 미리 정의된 종료값을 가진 모든 타겟 기반 래퍼 클래스이다
즉, 종료값이 사전에 정의된 애니메이션으로 DecayAnimation(감쇠)과 차이가 있다
TargetBasedAnimation는 시작 값과 속도, 종료 값이 애니메이션이 진행되는 동안 변경되지 않는 다고 가정하며, 이러한 값들을 캐시한다.
이러한 캐싱덕분에 애니메이션 값과 속도를 편리하게 조회할 수 있으며, 각 메서드에 단순히 시간만 전달하면 해당 시간에 따른 값을 얻을 수 있다
📌 타겟 기반 애니메이션이 중단될 경우, 현재 값과 속도를 시작 조건으로 사용하는 새로운 객체를 생성해야 한다.
이러한 중단 처리 방식은 Animatable과 Transition에서의 기본 동작이기 때문에 이 두가지 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"
}
}