本文为稀土技术社区首发签约文章,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

实践上除了animateFloatAsStateanimateColorAsState做了额外处理后再调用 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 的参数发现多了两个参数:typeConvertervisibilityThreshold,再看 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 是一个接口类型,有两个泛型 TV ,其间T为数据类型,VAnimationVector的子类。然后界说了两个办法:convertToVectorconvertFromVector,其作用是将数据类型与 AnimationVector进行相互转化,即 TV相互转化。

持续往下看 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进行检查:

Android Compose 动画使用详解(三)自定义animateXxxAsState动画

发现只要四个完成类,别离是 AnimationVector1DAnimationVector2DAnimationVector3D 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。那么假如是AnimationVector2DAnimationVector3D AnimationVector4D则对应别离有 2、3、4 个 value 特点,对应的 size 也应该别离是 2、3、4,经过检查源码发现也的确如此,这儿就不贴对应的源码了,大家有兴趣可自行检查源码。

了解了 TwoWayConverterAnimationVector后咱们再回到 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()参数而无需额外转化。

下面就来自界说这个 animateDpSizeAsStateApi,首先模仿上面的 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
        }
    )
}

完成作用:

Android Compose 动画使用详解(三)自定义animateXxxAsState动画

跟之前经过 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 个特点,那怎么办呢?

首先想到的是自界说一个承继自 AnimationVectorAnimationVector5D类,可是 AnimationVector是一个关闭类,不能在他自身 module 外承继运用,即不能在咱们的项目中承继AnimationVector,所以这条路行不通。

再来看上面的 5 个变量,其间 backgroundColorValue 是 Color 类型,而关于 Color 类型 Compose 自身供给了 Color.VectorConverter 用于 animateColorAsState 运用,其内部用到的 AnimationVectorAnimationVector4D,源码如下:

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))
        }
    }
}

终究完成作用如下:

Android Compose 动画使用详解(三)自定义animateXxxAsState动画

跟上一篇的完成作用一致,阐明代码没问题。

最终

本篇经过 animateDpAsState的源码分析对 animateValueAsState 的运用进行了探究,并具体介绍了怎么经过 animateValueAsState完成自界说 animateXxxAsState动画 Api。关于 animateXxxAsState 的运用就只剩下了 animationSpec参数的运用了,鄙人一篇将具体介绍 animationSpec动画装备的运用,让咱们的动画更加灵活,比如设置动画时长等,敬请期待。