1. Animatable和animateDpAsState的区别是什么
Animatable
是Android Compose
动画的底层API
,如果我们查看源码,可以发现animateDpAsState
内部是调用的animateValueAsState
,而animateValueAsState
内部调用的是Animatable
animateDpAsState
比Animatable
更便捷易用,但屏蔽了部分功能,animateDpAsState
抛弃了设置初始值的功能。
animateDpAsState
是对于动画具体场景化的一种实现,它针对状态切换这一种动画的场景,设置了专门的扩展,而状态切换是不需要初始值的。
如果想要更高的可定制性(比如设置初始值),那么就使用Animatable
2. animateDpAsState实现动画
首先,我们使用animateDpAsState
来实现一个简单的动画
var big by remember {
mutableStateOf(false)
}
val anim = animateDpAsState(if (big) 100.dp else 50.dp)
Box(modifier = Modifier
.size(anim.value)
.background(Color.Blue)
.clickable {
big = !big })
3. 使用Animatable实现上述动画
var big by remember {
mutableStateOf(false)
}
val size = remember(big) {
if (big) 100.dp else 50.dp
}
val anim1 = remember {
Animatable(size, Dp.VectorConverter) }
LaunchedEffect(key1 = big, block = {
anim1.animateTo(size)
})
Box(modifier = Modifier
.size(anim1.value)
.background(Color.Blue)
.clickable {
big = !big })
可以发现效果是一样的
这里需要注意的点有
Dp.VectorConverter
: 是TwoWayConverter<T, V>
类型,用来进行数值转换LaunchedEffect
: 是Compose
中的协程,animateTo
是一个挂起函数,需要在协程中使用
接下来我们来逐个讲解
3.1 TwoWayConverter
Animatable
中默认的TwoWayConverter
是Float.VectorConverter
fun Animatable(
initialValue: Float,
visibilityThreshold: Float = Spring.DefaultDisplacementThreshold
) = Animatable(
initialValue,
Float.VectorConverter,
visibilityThreshold
)
可以看到Float.VectorConverter
是TwoWayConverter<Float, AnimationVector1D>
类型,说明是直接转换成Float
的
val Float.Companion.VectorConverter: TwoWayConverter<Float, AnimationVector1D>
get() = FloatToVector
private val FloatToVector: TwoWayConverter<Float, AnimationVector1D> =
TwoWayConverter({
AnimationVector1D(it) }, {
it.value })
3.2 Animatable 支持 多种转换
Animatable
不仅支持Float
,还支持多种转换
//Float (默认不传就是Float类型)
val animatableFloat1 = remember {
Animatable(100F) }
val animatableFloat2 = remember {
Animatable(100F,Float.VectorConverter) }
//DP
val animatableDp = remember{
Animatable(100.dp,Dp.VectorConverter) }
//Int
val animatableInt = remember {
Animatable(100, Int.VectorConverter) }
//Rect
val animatableRect =
remember {
Animatable(Rect(100F, 100F, 100F, 100F), Rect.VectorConverter) }
//Offset
val animatableOffset = remember {
Animatable(Offset(100F, 100F), Offset.VectorConverter)
}
//IntOffset
val animatableIntOffset = remember {
Animatable(IntOffset(100, 100), IntOffset.VectorConverter)
}
//Size
val animatableSize = remember {
Animatable(Size(100F, 100F), Size.VectorConverter)
}
//IntSize
val animatableIntSize = remember {
Animatable(IntSize(100, 100), IntSize.VectorConverter)
}
val blackColor = Color(0xFF000000)
val converter = remember(blackColor.colorSpace) {
(Color.VectorConverter)(blackColor.colorSpace)
}
//Color
val animatableColor = remember {
Animatable(blackColor, converter)
}
3.3 1D、2D、3D和4D的区别
val animatableDp = remember{
Animatable(100.dp,Dp.VectorConverter) }
我们以Dp.VectorConverter
为例,可以发现AnimationVector1D
后缀带有1D
字样,这是啥意思呢?
val Dp.Companion.VectorConverter: TwoWayConverter<Dp, AnimationVector1D>
get() = DpToVector
D
代表的是维度
,即dimension
的意思。1D、2D、3D、4D
分别代表着一维、二维、三维、四维
val animatableOffset = remember {
Animatable(Offset(100F, 100F), Offset.VectorConverter)
}
比如这个Offset
就是二维的
val Offset.Companion.VectorConverter: TwoWayConverter<Offset, AnimationVector2D>
get() = OffsetToVector
4. Compose中的协程
Animatable
中的animateTo
是一个挂起函数,需要在协程中使用。
4.1 之前协程的写法
lifecycleScope.launch {
//TODO
}
4.2 LaunchedEffect
但是在Compose
里面,不能这么写,因为这种用法,没有针对Compose
做优化,在Compose
重组过程中,会重复进行调用。
所以Compose
提供了一个专门在Compose
中启动协程的API : LaunchedEffect
LaunchedEffect(key1 = 123, block = {
//代码块
anim1.animateTo(100.dp)
})
key不变的话可以这么写 : LaunchedEffect(Unit, block = {})
这里,只要key
没有发生变化,那么block
代码块就不会再次执行。这里也可以有多个key
LaunchedEffect(key1 = 123,key2=456,key3=789, block = {
})
如果有让LaunchedEffect
多次执行的场景,那么key1
参数就需要可以变化
到这里,我们也就能看懂 3. 使用Animatable实现上述动画
中的代码了
var big by remember {
mutableStateOf(false)
}
val size = remember(big) {
if (big) 100.dp else 50.dp
}
val anim1 = remember {
Animatable(size, Dp.VectorConverter) }
LaunchedEffect(key1 = big, block = {
anim1.animateTo(size)
})
Box(modifier = Modifier
.size(anim1.value)
.background(Color.Blue)
.clickable {
big = !big })
5. 使用Animatable进行流程定制
Animatable
对于动画是高度可定制化的,我们可以通过Animatable
来进行动画流程的定制
var big by remember {
mutableStateOf(false)
}
val size = remember(big) {
if (big) 100.dp else 50.dp
}
val anim1 = remember {
Animatable(size, Dp.VectorConverter) }
LaunchedEffect(key1 = big, block = {
//流程定制
anim1.animateTo(10.dp)
anim1.animateTo(200.dp)
anim1.animateTo(size)
})
Box(modifier = Modifier
.size(anim1.value)
.background(Color.Blue)
.clickable {
big = !big })
比如这里会先动画执行到10.dp
,然后再执行到200.dp
,最后再执行目标的size
效果如下所示
6. snapTo 瞬间完成动画
snapTo
会瞬间完成动画,就和没有动画的效果进行变化是一样的。
那这个方法有啥用呢 ?
可以在开始动画前先瞬间设置好初始动画状态,从而达到设置动画初始值的效果。
var big by remember {
mutableStateOf(false)
}
val size = remember(big) {
if (big) 100.dp else 50.dp
}
val anim1 = remember {
Animatable(size, Dp.VectorConverter) }
LaunchedEffect(key1 = big, block = {
//代码块
anim1.snapTo(size) //瞬间完成 ---> 设置初始值
})
Box(modifier = Modifier
.size(anim1.value)
.background(Color.Blue)
.clickable {
big = !big })
效果如下所示
7. animateDecay 惯性衰减动画
animateDecay
是惯性衰减动画,比如惯性滑动操作,可以实现和Android自带的惯性滑动一致的效果。
那和animateTo
有啥区别呢 ?
最大的区别在于 animateTo
是需要设置目标值的,也就是动画结束的那一刻 某个view
属性的值 必须明确指定,
而惯性衰减动画 animateDecay
则不需要指定。
animateDecay
: 从初始速度慢慢停下来,例如松手之后的惯性滑动animateTo
: 指定结束的属性值
val anim = remember {
Animatable(0.dp, Dp.VectorConverter)
}
val decay = remember {
exponentialDecay<Dp>()
}
LaunchedEffect(key1 = Unit, block = {
delay(1000L)
anim.animateDecay(2000.dp, decay) {
//动画监听,可以获取动画当前的值 this.value
}
})
Box(
modifier = Modifier
.padding(0.dp, anim.value, 0.dp, 0.dp)
.size(50.dp)
.background(Color.Blue)
)
效果如下所示
7.1 两种decay的区别
exponentialDecay
和rememberSplineBasedDecay
都可以实现惯性衰减动画,但它们两个有什么区别呢 ?
splineBasedDecay
: 一般情况下只有在使用像素的情况下,会使用这个,它不会做针对像素做修正的,多个设备滑动效果会不一致exponentialDecay
:其他情况下一般都是用这个,不会根据像素密度变化而变化,比如DP
,颜色,角度之类的
7.2 为什么rememberSplineBasedDecay
自带remember
我们可以发现,rememberSplineBasedDecay
是自带remember
的,但是exponentialDecay
却没有自带remember
,为什么会这么设计呢 ? 我们来分别看一下rememberSplineBasedDecay
的源码
rememberSplineBasedDecay
@Composable
actual fun <T> rememberSplineBasedDecay(): DecayAnimationSpec<T> {
val density = LocalDensity.current
return remember(density.density) {
SplineBasedFloatDecayAnimationSpec(density).generateDecayAnimationSpec()
}
}
可以看到remember
中传入了density
这个参数,说明rememberSplineBasedDecay
会随着density
像素密度的改变而改变。
而 exponentialDecay
则因为不会响应系统的变化,所以不需要在remember
中传入density
,就由使用者自己来包装remember
就好了。
7.3 animateDecay中的block监听
suspend fun animateDecay(
initialVelocity: T,
animationSpec: DecayAnimationSpec<T>,
block: (Animatable<T, V>.() -> Unit)? = null
): AnimationResult<T, V>
animateDecay
中有个block
的监听,动画发生变化的时候,会回调这个监听。
通过this.value
能够取到当前的动画值。
我们可以通过这个block
回调,来让其他view
响应这个动画的变化。
LaunchedEffect(key1 = Unit, block = {
anim.animateDecay(2000.dp, decay) {
//动画监听,可以获取动画当前的值 this.value
}
})
8. Compose 动画系列
Compose 动画系列,后续持续更新
Compose 动画 (一) : animateXxxAsState 实现放大/缩小/渐变等效果
Compose 动画 (二) : 为什么animateDpAsState要用val ? MutableState和State有什么区别 ?
Compose 动画 (三) : AnimatedVisibility 从入门到深入
Compose 动画 (四) : AnimatedVisibility 各种入场和出场动画效果
Compose 动画 (五) : animateContentSize / animateEnterExit / Crossfade / AnimatedContent
Compose 动画 (六) : 使用Transition管理多个动画,实现动画预览