本文为稀土技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
前言
上一篇《状况改变动画animateXxxAsState》介绍了 animateXxxAsState 系列动画 Api 的基本运用,但其间还有一个 animateValueAsState
的 Api 并没有介绍,实践上一篇介绍的一切 animateXxxAsState 系列动画 Api 终究内部调用的都是 animateValueAsState
,那么这一篇咱们就来具体了解一下 animateValueAsState
的作用及其怎么经过 animateValueAsState
来完成自界说 animateXxxAsState 动画 Api。
animateValueAsState 运用探究
上面提到其他 animateXxxAsState 系列 Api 终究都是调用 animateValueAsState
,那么咱们就以 animateDpAsState
为例来看一下是不是这样,检查animateDpAsState
的源码:
@Composable
fun animateDpAsState(
targetValue: Dp,
animationSpec: AnimationSpec<Dp> = dpDefaultSpring,
finishedListener: ((Dp) -> Unit)? = null
): State<Dp> {
return animateValueAsState(
targetValue,
Dp.VectorConverter,
animationSpec,
finishedListener = finishedListener
)
}
经过源码发现,animateDpAsState
的完成其实便是直接调用了animateValueAsState
。
实践上除了
animateFloatAsState
、animateColorAsState
做了额外处理后再调用animateValueAsState
外,其他 animateXxxAsState 的 Api 的完成都是直接调用animateValueAsState
,可检查对应源码具体了解
先看一下animateValueAsState
界说:
@Composable
fun <T, V : AnimationVector> animateValueAsState(
targetValue: T,
typeConverter: TwoWayConverter<T, V>,
animationSpec: AnimationSpec<T> = remember {
spring(visibilityThreshold = visibilityThreshold)
},
visibilityThreshold: T? = null,
finishedListener: ((T) -> Unit)? = null
): State<T>
对比 animateDpAsState
的参数发现多了两个参数:typeConverter
和 visibilityThreshold
,再看 animateDpAsState
中的调用 visibilityThreshold
参数是没传的,即运用的是默认值 null
,那么要害就在于 typeConverter
参数。
visibilityThreshold
为动画显示阈值,将在后续文章中进行具体介绍,本篇将不做过多论述,请持续重视本专栏后续文章。
typeConverter
是一个 TwoWayConverter
类型,源码如下:
/**
* [TwoWayConverter] class contains the definition on how to convert from an arbitrary type [T]
* to a [AnimationVector], and convert the [AnimationVector] back to the type [T]. This allows
* animations to run on any type of objects, e.g. position, rectangle, color, etc.
*/
interface TwoWayConverter<T, V : AnimationVector> {
/**
* Defines how a type [T] should be converted to a Vector type (i.e. [AnimationVector1D],
* [AnimationVector2D], [AnimationVector3D] or [AnimationVector4D], depends on the dimensions of
* type T).
*/
val convertToVector: (T) -> V
/**
* Defines how to convert a Vector type (i.e. [AnimationVector1D], [AnimationVector2D],
* [AnimationVector3D] or [AnimationVector4D], depends on the dimensions of type T) back to type
* [T].
*/
val convertFromVector: (V) -> T
}
TwoWayConverter
是一个接口类型,有两个泛型 T
和 V
,其间T
为数据类型,V
为 AnimationVector
的子类。然后界说了两个办法:convertToVector
和 convertFromVector
,其作用是将数据类型与 AnimationVector
进行相互转化,即 T
和 V
相互转化。
持续往下看 AnimationVector
的源码:
sealed class AnimationVector {
// 重置
internal abstract fun reset()
// 创立新的实例
internal abstract fun newVector(): AnimationVector
// 经过 index 获取对应的值
internal abstract operator fun get(index: Int): Float
// 经过 index 设置对应的值
internal abstract operator fun set(index: Int, value: Float)
// 数据特点数量
internal abstract val size: Int
}
能够看出 AnimationVector
是一个密封类,供给了一些列的笼统办法和特点,具体可看上面对应注释。既然是个关闭类,那就看看其对应的完成类有哪些,经过 Ctrl/Command + Alt + B
进行检查:
发现只要四个完成类,别离是 AnimationVector1D
、AnimationVector2D
、AnimationVector3D
AnimationVector4D
,即一维、二维、三维、四维动画。
检查期中 AnimationVector1D
的源码:
class AnimationVector1D(initVal: Float) : AnimationVector() {
/**
* This field holds the only Float value in this [AnimationVector1D] object.
*/
var value: Float = initVal
internal set
// internal
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
override fun toString(): String {
return "AnimationVector1D: value = $value"
}
override fun equals(other: Any?): Boolean =
other is AnimationVector1D && other.value == value
override fun hashCode(): Int = value.hashCode()
}
完成很简单,这儿着重介绍一下 value 和 size,其对应的是动画的数据值和数据数量,由于一维动画只要一个数据,所以这儿只界说了一个 value 特点,对应的 size 的值也为 1。那么假如是AnimationVector2D
、AnimationVector3D
AnimationVector4D
则对应别离有 2、3、4 个 value 特点,对应的 size 也应该别离是 2、3、4,经过检查源码发现也的确如此,这儿就不贴对应的源码了,大家有兴趣可自行检查源码。
了解了 TwoWayConverter
和 AnimationVector
后咱们再回到 animateDpAsState
的源码,在 animateDpAsState
中调用 animateValueAsState
时其间typeConverter
参数传入的是 Dp.VectorConverter
,来看一下 Dp.VectorConverter
的完成:
val Dp.Companion.VectorConverter: TwoWayConverter<Dp, AnimationVector1D>
get() = DpToVector
private val DpToVector: TwoWayConverter<Dp, AnimationVector1D> = TwoWayConverter(
convertToVector = { AnimationVector1D(it.value) },
convertFromVector = { Dp(it.value) }
)
Dp.VectorConverter
实践回来的是 DpToVector
,而 DpToVector
又是经过 TwoWayConverter
创立的,看到这儿估计许多同学就疑惑了,不对呀,上面不是说 TwoWayConverter
是一个接口么,咋还能直接创立一个实例呢?咱们点进去检查一下这儿 TwoWayConverter
的源码:
fun <T, V : AnimationVector> TwoWayConverter(
convertToVector: (T) -> V,
convertFromVector: (V) -> T
): TwoWayConverter<T, V> = TwoWayConverterImpl(convertToVector, convertFromVector)
发现这儿的 TwoWayConverter
并不是上面介绍的接口类型而是一个办法,办法回来的是 TwoWayConverterImpl
,而 TwoWayConverterImpl
才是上面提到的 TwoWayConverter
接口的完成类:
private class TwoWayConverterImpl<T, V : AnimationVector>(
override val convertToVector: (T) -> V,
override val convertFromVector: (V) -> T
) : TwoWayConverter<T, V>
实践上是将 TwoWayConverter
接口的两个办法抽成了两个参数传入进来,这样让运用时更加方便。
到这儿咱们就理解了为啥 DpToVector
是经过一个 TwoWayConverter
办法创立的了,其间转化为 Vector 是经过 AnimationVector1D(it.value)
创立一个 AnimationVector 实例,其间 it 即为 Dp 类型的数据实例;而将 Vector 转化为 Dp 则是直接经过 Dp(it.value)
创立一个 Dp 类型的实例,其间 it 为 AnimationVector 类型,即上面创立的 AnimationVector1D 目标,这样就完成了 Dp 与 AnimationVector1D 的相互转化。
其他 animateXxxAsState 的 Api 与 animateDpAsState 的完成方法基本类似,这儿就不重复介绍了,感兴趣的同学可经过源码检查了解更多。
自界说 animateXxxAsState
了解了 animateDpAsState
是怎么调用 animateValueAsState
完成的以后,下面咱们就按照同样的思路和流程就能够来完成一个自界说的 animateXxxAsState
动画 Api 了。
上一篇咱们完成改变组件大小时运用的是 animateSizeAsState
,由于 Modifier.size()
接纳的是一个 DpSize
类型参数而不是 Size,所以并不能直接传入 Size 数据,而是需要进行转化后运用,上一篇的完成如下:
@Composable
fun SizeAnimationBox() {
var changeSize by remember { mutableStateOf(false) }
// 界说 Size 动画
val size by animateSizeAsState(if (changeSize) Size(200f, 50f) else Size(100f, 100f))
Box(Modifier
.padding(10.dp)
// 设置 Size 值
.size(size.width.dp, size.height.dp)
.background(Color.Blue)
.clickable {
changeSize = !changeSize
}
)
}
咱们期望的是有一个animateDpSizeAsState
办法直接回来一个 State<DpSize>
数据,这样就能直接将其用于 Modifier.size()
参数而无需额外转化。
下面就来自界说这个 animateDpSizeAsState
Api,首先模仿上面的 Dp.VectorConverter
完成DpSize.VectorConverter
,如下:
val DpSize.Companion.VectorConverter: TwoWayConverter<DpSize, AnimationVector2D>
get() = DpSizeToVector
private val DpSizeToVector: TwoWayConverter<DpSize, AnimationVector2D> = TwoWayConverter(
// 创立 AnimationVector2D 别离传入 DpSize 的宽和高
convertToVector = { AnimationVector2D(it.width.value, it.height.value) },
// 创立 DpSize 将 AnimationVector2D 的 v1 和 v2 转化为 Dp 传入 DpSize 的宽高
convertFromVector = { DpSize(Dp(it.v1), Dp(it.v2)) }
)
由于 DpSize 有宽和高两个特点,所以这儿需要运用 AnimationVector2D 来进行转化。
然后创立 animateDpSizeAsState
办法:
@Composable
fun animateDpSizeAsState(
targetValue: DpSize,
finishedListener: ((DpSize) -> Unit)? = null
): State<DpSize> {
return animateValueAsState(
targetValue,
DpSize.VectorConverter,
finishedListener = finishedListener
)
}
直接调用 animateValueAsState
传入上面界说的 DpSize.VectorConverter
即可,然后将之前的 animateSizeAsState
创立的动画换成运用 animateDpSizeAsState
,代码如下:
@Composable
fun DpSizeAnimationBox() {
var changeSize by remember { mutableStateOf(false) }
// 创立目标值的 DpSize 目标
val targetValue = if (changeSize) DpSize(200.dp, 50.dp) else DpSize(100.dp, 100.dp)
val size by animateDpSizeAsState(targetValue)
Box(Modifier
.padding(10.dp)
// 直接运用 animateDpSizeAsState 回来的 size
.size(size)
.background(Color.Blue)
.clickable {
changeSize = !changeSize
}
)
}
完成作用:
跟之前经过 animateSizeAsState
完成的作用相同,可是代码却简洁了许多,这便是自界说 animateXxxAsState 的优点。
实战
了解了自界说 animateXxxAsState
的完成,下面再将上一篇完成的上传按钮动画经过自界说 animateXxxAsState
的方法来完成。
关于上传按钮作用的具体完成思路可检查上一篇《状况改变动画animateXxxAsState》进行了解
上一篇咱们在完成上传按钮作用时界说了 5 个变量,如下:
// 文字透明度
var textAlphaValue = 1f
// 按钮色彩
var backgroundColorValue = Color.Blue
// 按钮宽度
var boxWidthValue = originWidth
// 进展透明度
var progressAlphaValue = 0f
// 进展值
var progressValue = 0
经过上面对自界说 animateXxxAsState
的了解,咱们需要创立 AnimationVector
,而 Compose 只给咱们供给了最多 4 个数据特点的 AnimationVector4D
,但这儿却有 5 个特点,那怎么办呢?
首先想到的是自界说一个承继自 AnimationVector
的 AnimationVector5D
类,可是 AnimationVector
是一个关闭类,不能在他自身 module 外承继运用,即不能在咱们的项目中承继AnimationVector
,所以这条路行不通。
再来看上面的 5 个变量,其间 backgroundColorValue 是 Color 类型,而关于 Color 类型 Compose 自身供给了 Color.VectorConverter
用于 animateColorAsState
运用,其内部用到的 AnimationVector
是 AnimationVector4D
,源码如下:
val ColorToVector: (colorSpace: ColorSpace) -> TwoWayConverter<Color, AnimationVector4D>
所以这么算的话 Color 类型其实相当于是 4 个数据值,那上面 5 个变量假如要封装成一个实体类的话,相当于实践有 8 个数据值,但又没有 AnimationVector8D
又不能自己承继完成,那么就只能将 backgroundColor 单独提出来,然后把其他 4 个变量封装成一个实体类,如下:
data class UploadValue(val textAlpha : Float, val boxWidth : Dp, val progress:Int, val progressAlpha:Float){
companion object
}
然后创立对应的 VectorConverter
:
val UploadValue.Companion.VectorConverter: TwoWayConverter<UploadValue, AnimationVector4D>
get() = UploadToVector
private val UploadToVector: TwoWayConverter<UploadValue, AnimationVector4D> = TwoWayConverter(
convertToVector = { AnimationVector4D(it.textAlpha, it.boxWidth.value, it.progress.toFloat(), it.progressAlpha) },
convertFromVector = { UploadValue(it.v1, Dp(it.v2), it.v3.toInt(), it.v4) }
)
再创立 animateUploadAsState
办法:
@Composable
fun animateUploadAsState(
targetValue: UploadValue,
finishedListener: ((UploadValue) -> Unit)? = null
): State<UploadValue> {
return animateValueAsState(
targetValue,
UploadValue.VectorConverter,
finishedListener = finishedListener
)
}
最终将代码完成换成 animateUploadAsState
:
@Composable
fun UploadNewAnimation() {
val originWidth = 180.dp
val circleSize = 48.dp
var uploadState by remember { mutableStateOf(UploadState.Normal) }
var text by remember { mutableStateOf("Upload") }
// 依据状况创立 UploadValue 实体
val uploadValue = when (uploadState) {
UploadState.Normal -> UploadValue(1f, originWidth, 0, 0f)
UploadState.Start -> UploadValue(0f, circleSize, 0, 1f)
UploadState.Uploading -> UploadValue(0f, circleSize, 100, 1f)
UploadState.Success -> UploadValue(1f, originWidth, 100, 0f)
}
// 依据状况创立背景色彩
val backgroundColorValue = when (uploadState) {
UploadState.Normal -> Color.Blue
UploadState.Start -> Color.Gray
UploadState.Uploading -> Color.Gray
UploadState.Success -> Color.Red
}
// 创立 UploadValue 的 State
val upload by animateUploadAsState(uploadValue){
// 监听动画完成修正状况
if(uploadState == UploadState.Start){
uploadState = UploadState.Uploading
}else if(uploadState == UploadState.Uploading){
uploadState = UploadState.Success
text = "Success"
}
}
val backgroundColor by animateColorAsState(backgroundColorValue)
Box(
modifier = Modifier
.padding(start = 10.dp, top = 10.dp)
.width(originWidth),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(circleSize / 2))
.background(backgroundColor)
// 替换为运用 upload.boxWidth
.size(upload.boxWidth, circleSize)
.clickable {
uploadState = UploadState.Start
},
contentAlignment = Alignment.Center,
) {
Box(
// 替换为运用 upload.progress
modifier = Modifier.size(circleSize).clip(ArcShape(upload.progress))
// 替换为运用 upload.progressAlpha
.alpha(upload.progressAlpha).background(Color.Blue)
)
Box(
modifier = Modifier.size(40.dp).clip(RoundedCornerShape(20.dp))
// 替换为运用 upload.progressAlpha
.alpha(upload.progressAlpha).background(Color.White)
)
// 替换为运用 upload.textAlpha
Text(text, color = Color.White, modifier = Modifier.alpha(upload.textAlpha))
}
}
}
终究完成作用如下:
跟上一篇的完成作用一致,阐明代码没问题。
最终
本篇经过 animateDpAsState
的源码分析对 animateValueAsState
的运用进行了探究,并具体介绍了怎么经过 animateValueAsState
完成自界说 animateXxxAsState
动画 Api。关于 animateXxxAsState
的运用就只剩下了 animationSpec
参数的运用了,鄙人一篇将具体介绍 animationSpec
动画装备的运用,让咱们的动画更加灵活,比如设置动画时长等,敬请期待。