Compose Compose动画之DecayAnimation
- DecayAnimation的使用
- 参数block的作用
DecayAnimation
上一篇Compose动画之AnimateSpec介绍了多个常用的AnimationSpec
,它们都是作用在animateTo函数上的,比如weenSpec
anim.animateTo(96.dp, TweenSpec(easing = LinearEasing))
本篇介绍一个和它们不太一样的AnimationSpec
- DecayAnimationSpec
,Decay
是衰变的意思。
它主要是作用于惯性类的动画,和之前介绍的动画有个很大的区别是,它是不用指定终点(目标)值的,需要指定的是初始速度。
其实从物理世界的角度还是很容易理解的,一个惯性动作的终点是由开始那一刻的速度决定,至于目标到哪里停下来,是由外界因素(比如摩擦力)影响。
所以,对于这种动画效果,之前使用的animateTo
就不合适了。要使用animateDecay
函数:
suspend fun animateDecay(
initialVelocity: T,
animationSpec: DecayAnimationSpec<T>, // look it
block: (Animatable<T, V>.() -> Unit)? = null
): AnimationResult<T, V> {
val anim = DecayAnimation(
animationSpec = animationSpec,
initialValue = value,
initialVelocityVector = typeConverter.convertToVector(initialVelocity),
typeConverter = typeConverter
)
return runAnimation(anim, initialVelocity, block)
}
可以清晰的看到,第一个参数是初始速度initialVelocity
,第二个参数就是DecayAnimationSpec
initialVelocity
初始速度这个参数,也是值得一说的。它的类型是泛型T
,说明是支持多种类型的。
比如常用的DP,如果指定1000dp,就代表初始速度是每秒1000dp
DecayAnimationSpec
针对第二个参数,官方也给封装了两个常用的函数:splineBasedDecay
和 exponentDecay
1.splineBasedDecay
:基于样条的衰减
fun <T> splineBasedDecay(density: Density): DecayAnimationSpec<T> =
SplineBasedFloatDecayAnimationSpec(density).generateDecayAnimationSpec()
需要理解一下为什么需要density
这个参数:
首先需要知道这个函数是针对像素的。对于不同像素密度的手机来说,如果初始速度一致,像素密度高的,那么停下来的距离(屏幕的物理尺寸)必然短。所以为了保证在不同设备上的效果一致,就必须根据屏幕的密度做相应的调整。所以这个参数的意义就在于此。
由于这个参数获取方式很统一,所以官方也封装了一种更简单的函数rememberSplineBasedDecay
:
@Composable
actual fun <T> rememberSplineBasedDecay(): DecayAnimationSpec<T> {
val density = LocalDensity.current
return remember(density.density) {
SplineBasedFloatDecayAnimationSpec(density).generateDecayAnimationSpec()
}
}
该函数使用了remember
,因为尺寸有可能会在使用过程中被用户更改,所以在被更改时,也要做相应的变化。
2.exponentDecay
:指数式衰减
fun <T> exponentialDecay(frictionMultiplier: Float = 1f,absVelocityThreshold: Float = 0.1f): DecayAnimationSpec<T> =
FloatExponentialDecaySpec(frictionMultiplier, absVelocityThreshold).generateDecayAnimationSpec()
这两个参数还是比较好理解的:
frictionMultiplier
:摩擦乘数,指示动画停止的速度。该值要大于0 ,默认值为1.0absVelocityThreshold
: 动画被认为足够接近以停止动画完成的速度。
指数函数是无限趋近于0,所以第二个参数absVelocityThreshold
的目的就是给个阈值速度提前结束动画。一半情况使用默认提供的值就可以了。
区分两者的使用场景
1.splineBasedDecay
基于样条的衰减: 用于像素单位的衰减,由于内部会根据密度做校正,所以如果使用其他单位,会出现不可预期的效果。
2.exponentDecay
:指数式衰减,适用于像素外的其他单位,内部不做任何校正。适用场景广,一般都用这个。
代码示例:对一个红色方块做衰减式动画
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val anim = remember { Animatable(0.dp, Dp.VectorConverter) }
// 基于样条的衰减: 用于像素单位的衰减
val splineBasedDecay = rememberSplineBasedDecay<Dp>()
// 指数式衰减:适用范围广,一般使用这个
val exponentDecay = exponentialDecay<Dp>()
LaunchedEffect(key1 = Unit) {
delay(1000)
// 以每秒1000dp的速度
// anim.animateDecay(1000.dp, splineBasedDecay)
anim.animateDecay(1000.dp, exponentDecay)
}
Box(
Modifier
.padding(0.dp, top = anim.value, 0.dp, 0.dp) // 先padding后background:margin的效果
.size(100.dp)
.background(Color.Red)
)
}
}
参数block的作用
在介绍animateDecay
函数参数时,略过了第三个参数block
,在Compose动画之AnimateSpec中介绍animateTo
函数中也是存在这个参数的:
suspend fun animateDecay(
initialVelocity: T,
animationSpec: DecayAnimationSpec<T>, // look it
block: (Animatable<T, V>.() -> Unit)? = null
): AnimationResult<T, V>
这个block
函数参数,作用是用来监听动画的每一帧。比如在上述的示例代码中,再添加一个蓝色方块跟着红色方块移动,就可以在block函数体
中监听红色方块动画过程,不断更新蓝色方块的位置,实现两个方块的同步移动:
代码示例:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val anim = remember { Animatable(0.dp, Dp.VectorConverter) }
var padding by remember { mutableStateOf(anim.value) } // 控制蓝色方块的位置
// 指数式衰减:适用范围广,旋转
val exponentDecay = exponentialDecay<Dp>()
LaunchedEffect(key1 = Unit) {
delay(1000)
anim.animateDecay(1000.dp, exponentDecay, block = {
Log.d("AnimateDecay", "animate block: $value")
// 目标(⭐️):跟随红色的动画过程移动
padding = value
})
}
Row {
Box(
Modifier
.padding(
0.dp,
top = anim.value,
0.dp,
0.dp
) // 先padding后background:margin的效果
.size(100.dp)
.background(Color.Red)
)
Box(
Modifier
.padding(
0.dp,
top = padding,
0.dp,
0.dp
) // 先padding后background:margin的效果
.size(100.dp)
.background(Color.Blue)
)
}
}
}