compose动画从底层基础到顶层高级应用(四)核心API之--Animatable

150 阅读3分钟

前面学习了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,包含两个属性:

  1. endState: AnimationState<T, V>:动画在取消或重置之前的最后一帧中的状态。
  2. 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>

参数:

  1. initialVelocity:初始速度,惯性动画肯定需要一个初始速度去衰减。
  2. 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)