前面学习了AnimateAsState和Transition,这两者都是声明式API,也就是告诉compose我想要到达的目标是什么,compose会自动执行动画,我们能控制的只有状态以及通过SeekableTransitionState控制时钟,想要完全控制动画就需要今天要学习的Animatable类了。
今天的例子是实现一个拖动💗的动画,爱心会变大,松手后还会惯性运动一段时间再停下来,效果如下:
完整代码:Animatable示例
构造函数
@Suppress("NotCloseable")
public class Animatable<T, V : AnimationVector>(
initialValue: T,
public val typeConverter: TwoWayConverter<T, V>,
private val visibilityThreshold: T? = null,
public val label: String = "Animatable"
)
这几个参数在前面的文章中都已经见过了,不再赘述。 compose还提供了一个Float类型的公共方法来构造对象,如下:
public fun Animatable(
initialValue: Float,
visibilityThreshold: Float = Spring.DefaultDisplacementThreshold
): Animatable<Float, AnimationVector1D> =
Animatable(initialValue, Float.VectorConverter, visibilityThreshold)
构造对象
分别定义大小和位置的Animatable对象:
val offsetAnimate = remember {
Animatable(initialValue = Offset(0f, 0f), Offset.VectorConverter)
}
val scaleAnimate = remember { Animatable(initialValue = 1.0f) }
animateTo函数
suspend fun animateTo(
targetValue: T,
animationSpec: AnimationSpec<T> = defaultSpringSpec,
initialVelocity: T = velocity,
block: (Animatable<T, V>.() -> Unit)? = null
): AnimationResult<T, V>
参数都是老熟人了,其中block是一个回调方法,可以通过animatable对象获取它的属性。
返回值是AnimationResult,包含两个属性:
endState: AnimationState<T, V>:动画在取消或重置之前的最后一帧中的状态。endReason: AnimationEndReason:动画结束原因,Finished正常结束,BoundReached因达到设置的上下限而结束。
使用:
在触摸开始时触发变大动画。
onDragStart = {
//重置速度跟踪器,后续的惯性动画使用。
velocityTracker.resetTracking()
scope.launch {
//这里使用到了stop函数,比较简单。
offsetAnimate.stop()
//看这里,使用到animateTo函数触发动画。
scaleAnimate.animateTo(2.0f, tween(1000))
}
},
snapTo函数
suspend fun snapTo(targetValue: T) {
mutatorMutex.mutate {
endAnimation()
val clampedValue = clampToBounds(targetValue)
internalState.value = clampedValue
this.targetValue = clampedValue
}
}
snapTo函数只有一个targetValue参数,直接跳转到目标值,在拖动动画时使用,立刻反馈结果。
使用
拖动爱心时使用这个函数。
onDrag = { change, dragAmount ->
scope.launch {
//看这里,使用snapTo实时切换到新的位置。
offsetAnimate.snapTo(offsetAnimate.value + (dragAmount / density))
}
// 给速度跟踪器添加位置,后续的惯性动画使用。
velocityTracker.addPosition(change.uptimeMillis, change.position)
},
animateDecay函数
惯性动画函数,使用的动画规格就不是前面已经学习的有限和无限动画规格了,是一种特殊的动画。
suspend fun animateDecay(
initialVelocity: T,
animationSpec: DecayAnimationSpec<T>,
block: (Animatable<T, V>.() -> Unit)? = null
): AnimationResult<T, V>
参数:
initialVelocity:初始速度,惯性动画肯定需要一个初始速度去衰减。animationSpec:衰减动画规格,一般使用样条曲线的衰减函数:splineBasedDecay,最符合物理特性。
使用
拖动结束后使用这个函数。
onDragEnd = {
//获取拖动结束时的速度。
val velocity = velocityTracker.calculateVelocity().let {
Offset(-it.x, -it.y) / density
}
scope.launch {
//看这里,传入速度和衰减动画规格。
offsetAnimate.animateDecay(
velocity,
splineBasedDecay(this@pointerInput)
)
//恢复爱心的大小。
scaleAnimate.animateTo(1.0f, tween(1000))
}
}
主要就是这三个函数,下面介绍下公共的属性和其他公共方法。
isRunning
指示动画是否正在运行。
var isRunning: Boolean by mutableStateOf(false)
private set
lowerBound
动画的下限,通过updateBounds函数修改。
var lowerBound: T? = null
private set
upperBound
动画的上限,通过updateBounds函数修改。
var upperBound: T? = null
private set
targetValue
动画的目标值。
var targetValue: T by mutableStateOf(initialValue)
private set
value
动画的当前值。
val value: T
get() = internalState.value
velocityVector
动画的速度向量。
val velocityVector: V
get() = internalState.velocityVector
velocity
动画的速度。
val velocity: T
get() = typeConverter.convertFromVector(velocityVector)
asState
将Animatable对象转换为状态, 意味着animatable可以在viewmodel中设计,返回一个state给UI,UI根据state来重组。
fun asState(): State<T> = internalState
stop
停止当前动画。
suspend fun stop() {
mutatorMutex.mutate {
endAnimation()
}
}
updateBounds
更新动画的上下限。
fun updateBounds(lowerBound: T? = this.lowerBound, upperBound: T? = this.upperBound)