本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
本篇文章是此专栏的第六篇文章,前几篇文章大概将 Compose
中的动画都简单过了一遍,如果想阅读前几篇文章的话可以点击下方链接:
- Compose 动画艺术探索之瞅下 Compose 的动画
- Compose 动画艺术探索之可见性动画
- Compose 动画艺术探索之属性动画
- Compose 动画艺术探索之动画规格
- Compose 动画艺术探索之灵动岛
AnimationVector 是啥?
大家可能都知道或听说在 Compose
中动画使用起来很简单,但其实是使用起来很简单,内部逻辑其实也不简单。。。大家用了这么久的 Compose
,里面的动画也都使用了很多,但这个问题其实没有多少人能回答出来。
对啊,AnimationVector
是个啥呢?其实虽然不知道它是个啥,但其实我们却在一直使用它!来举个栗子🌰吧!
val colors by animateColorAsState(
Color.Red,
animationSpec = spring(Spring.StiffnessVeryLow)
)
val sizes by animateDpAsState(
15.dp,
animationSpec = spring(Spring.StiffnessVeryLow)
)
上面的代码熟悉么?看起来很简单,并且也经常使用,其实在第三篇文章 Compose 动画艺术探索之属性动画 中也提到了 AnimationVector
,其实说白了它就是个类型转换器,并没有什么神秘的。
AnimationVector
下面来直接看下 AnimationVector
的源码吧:
sealed class AnimationVector {
internal abstract fun reset()
internal abstract fun newVector(): AnimationVector
internal abstract operator fun get(index: Int): Float
internal abstract operator fun set(index: Int, value: Float)
internal abstract val size: Int
}
可以看到 AnimationVector
是一个密封类,它是 AnimationVector1D
, AnimationVector2D
, AnimationVector3D
和AnimationVector4D
的基类。为了动画任何任意类型,它需要提供一个 TwoWayConverter
来定义如何将任意类型 T 转换为 AnimationVector
,反之亦然。取决于这个类型 T 有多少维度,它可能需要转换为AnimationVector
的任何子类。例如,基于位置的对象应该转换为 AnimationVector2D
(x、y 的坐标),而描述矩形边界的对象应该转换为 AnimationVector4D
(左上右下的坐标)。
下面来看下 AnimationVector1D
的源码吧!
class AnimationVector1D(initVal: Float) : AnimationVector() {
// 这个字段保存了对象中唯一的Float值。
var value: Float = initVal
internal set
override fun reset() {
value = 0f
}
override fun newVector(): AnimationVector1D = AnimationVector1D(0f)
override fun get(index: Int): Float {
if (index == 0) {
return value
} else {
return 0f
}
}
override fun set(index: Int, value: Float) {
if (index == 0) {
this.value = value
}
}
override val size: Int = 1
}
可以看到 AnimationVector1D
类中的代码并不难理解,首先继承自 AnimationVector
,然后实现了几个抽象方法,一共有四个嘛,咱们再来看下 AnimationVector1D
就基本能知道这四个类的区别了。
class AnimationVector3D(v1: Float, v2: Float, v3: Float) : AnimationVector() {
var v1: Float = v1
internal set
var v2: Float = v2
internal set
var v3: Float = v3
internal set
override fun reset() {
v1 = 0f
v2 = 0f
v3 = 0f
}
override fun newVector(): AnimationVector3D = AnimationVector3D(0f, 0f, 0f)
override fun get(index: Int): Float {
return when (index) {
0 -> v1
1 -> v2
2 -> v3
else -> 0f
}
}
override fun set(index: Int, value: Float) {
when (index) {
0 -> v1 = value
1 -> v2 = value
2 -> v3 = value
}
}
override val size: Int = 3
}
其实内部和 AnimationVector1D
没啥区别,只不过多了两个参数而已。AnimationVector2D
和 AnimationVector4D
的代码猜也能猜到了,AnimationVector2D
中有两个参数,AnimationVectorD
中有四个参数嘛!大家可以去看看,就是想的那样!
TwoWayConverter
上面一直提到 TwoWayConverter
,那就来看看吧!
interface TwoWayConverter<T, V : AnimationVector> {
// 定义如何将类型 T 转换为向量类型
val convertToVector: (T) -> V
// 定义如何将向量类型转换为类型 T
val convertFromVector: (V) -> T
}
TwoWayConverter
是一个接口,包含了如何从任意类型 T 转换为 AnimationVector
,并将AnimationVector
转换回类型 T 的定义。这允许动画在任何类型的对象上运行,例如位置,矩形,颜色等。
到这里这些类的定义都走了一遍,那么咱们就来看看上面写的 animateDpAsState
中是如何使用 AnimationVector
的吧!
之前第三篇文章中提到过,Compose
中的属性动画最后都是调用的 animateValueAsState
,那就先来看下 animateValueAsState
吧!
@Composable
fun <T, V : AnimationVector> animateValueAsState(
targetValue: T,
typeConverter: TwoWayConverter<T, V>, // 转换器
animationSpec: AnimationSpec<T> = remember { spring() },
visibilityThreshold: T? = null,
label: String = "ValueAnimation",
finishedListener: ((T) -> Unit)? = null
)
是不是有 TwoWayConverter
!之前说这块的时候只是一带而过了,因为没有办法把所有的内容一次都说清楚,这样就没有重点或者说全是重点了。可以看到 typeConverter
是一个必须填写的参数,但是咱们在调用 animateXXXAsState
的时候并没有填写,为啥呢?很简单,官方帮我们将一些基本数据的转换都写好了,一起来看下!
@Composable
fun animateDpAsState(
targetValue: Dp,
animationSpec: AnimationSpec<Dp> = dpDefaultSpring,
label: String = "DpAnimation",
finishedListener: ((Dp) -> Unit)? = null
): State<Dp> {
return animateValueAsState(
targetValue,
Dp.VectorConverter, // 转换器
animationSpec,
label = label,
finishedListener = finishedListener
)
}
可以看到上面有一行重点代码:Dp.VectorConverter
,这个就构建了 animateValueAsState
中所需要的 TwoWayConverter
,下面就来看看是如何构建的吧!
val Dp.Companion.VectorConverter: TwoWayConverter<Dp, AnimationVector1D>
get() = DpToVector
private val DpToVector: TwoWayConverter<Dp, AnimationVector1D> = TwoWayConverter(
convertToVector = { AnimationVector1D(it.value) },
convertFromVector = { Dp(it.value) }
)
代码并不多,可以看到这里使用了 AnimationVector1D
,为啥呢?因为 Dp 确实只需要一个值就可以表示,转换方法也很简单,直接将 Dp 中的 value
值进行转换即可。再来看几个吧!
private val SizeToVector: TwoWayConverter<Size, AnimationVector2D> =
TwoWayConverter(
convertToVector = { AnimationVector2D(it.width, it.height) },
convertFromVector = { Size(it.v1, it.v2) }
)
private val RectToVector: TwoWayConverter<Rect, AnimationVector4D> =
TwoWayConverter(
convertToVector = {
AnimationVector4D(it.left, it.top, it.right, it.bottom)
},
convertFromVector = {
Rect(it.v1, it.v2, it.v3, it.v4)
}
)
可以看到上面代码是 Size
和 Rect
的转换,Size
需要宽高来进行描述,所以就使用的是 AnimationVector2D
,而 Rect
则需要四个值来分别表示左上右下的坐标点,所以就需要使用 AnimationVector4D
来描述。
自定义转换器
没错,咱们可以自定义转换器的!首先咱们来自定义一个类型吧!
data class RealSize(val length: Dp, val width: Dp, val height: Dp)
比如这块定义一个 RealSize
,里面有三个值分别表示长宽高,下面就来看看如何自定义转换器吧!
val realSize: RealSize by animateValueAsState<RealSize, AnimationVector3D>(
RealSize(10.dp, 20.dp, 30.dp),
TwoWayConverter(
convertToVector = { size: RealSize ->
// 从每个“Dp”字段中提取一个浮点值。
AnimationVector3D(size.length.value, size.width.value, size.height.value)
},
convertFromVector = { vector: AnimationVector3D ->
RealSize(vector.v1.dp, vector.v2.dp, vector.v3.dp)
}
)
)
是不是也不难,只需要将需要转换的值确定好,然后调用对用的 AnimationVectorXD
进行转换即可。
结尾
本篇文章主要带大家一起看一下 AnimationVector
,大部分使用的时候只关注如何使用,并没有看看里面的具体实现,Compose
虽好,可不要贪杯哦。简单的一些当然可以直接使用官方提供的,但如果复杂一些需要自定义的话还是看看比较好的。
本文至此结束,有用的地方大家可以参考,当然如果能帮助到大家,哪怕是一点也足够了。就这样。