本文为稀土技能社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

前言

上一篇介绍了 Animatable 动画以及其 animateTosnapTo两个敞开动画 api 的运用,实际上 Animatable 除了这两个 api 以外还有一个 animateDecay即本篇要介绍的衰减动画。

什么是衰减动画呢?便是动画速度由快到慢最终中止,最常见的运用场景便是惯性动画,比方滑动列表时手指松开后列表不会立即中止而是会继续滑动一段间隔后才中止;下面就来看看 animateDecay具体怎样运用。

animateDecay

首先还是来看一下 animateDecay的界说:

suspend fun animateDecay(
    initialVelocity: T,
    animationSpec: DecayAnimationSpec<T>,
    block: (Animatable<T, V>.() -> Unit)? = null
): AnimationResult<T, V> 

跟前面介绍的 animateTosnapTo相同都是 suspend润饰的办法,即必须在协程中调用,参数有三个,分别解析如下:

  • initialVelocity:初始速度
  • animationSpec:动画装备,DecayAnimationSpec类型
  • block:函数类型参数,动画运转的每一帧都会回调这个 block 办法,可用于动画监听

返回值跟 animateTo相同都是 AnimationResult类型。

initialVelocity是动画的初始速度,动画会从这个初始速度依照必定的衰减曲线进行衰减,直到速度为 0 或达到阈值时动画中止。那这个初始速度的单位是多少呢?是单位/秒 这儿的单位便是动画作用的数值类型,比方数值类型是 Dp,那就代表多少 Dp 每秒。

而衰减曲线的装备便是第二个参数 animationSpec,需求留意的是这儿的 animationSpecDecayAnimationSpec类型,它并不是前面介绍的 AnimationSpec的子类,是衰减动画特有的动画装备,看一下 DecayAnimationSpec 的界说:

interface DecayAnimationSpec<T> {
    fun <V : AnimationVector> vectorize(
        typeConverter: TwoWayConverter<T, V>
    ): VectorizedDecayAnimationSpec<V>
}

从源码能够知晓,DecayAnimationSpec是一个独立的接口,盯梢其完成类只要一个 DecayAnimationSpecImpl:

private class DecayAnimationSpecImpl<T>(
    private val floatDecaySpec: FloatDecayAnimationSpec
) : DecayAnimationSpec<T> {
    override fun <V : AnimationVector> vectorize(
        typeConverter: TwoWayConverter<T, V>
    ): VectorizedDecayAnimationSpec<V> = VectorizedFloatDecaySpec(floatDecaySpec)
}

这个完成类是 private的,也便是不能直接创立其实例,那怎样创立呢?Compose 供给三个办法用于创立,分别是 splineBasedDecayrememberSplineBasedDecayexponentialDecay,那么这三种办法又有什么区别呢?下面分别对其进行具体介绍。

splineBasedDecay

splineBasedDecay根据办法命名咱们能够翻译为根据样条曲线的衰减,什么是样条曲线呢?Google得到的答案:样条曲线是经过或接近影响曲线形状的一系列点的滑润曲线。更笼统了,实际上咱们并不需求了解他是怎样完成的,当然感兴趣的能够自行查询相关材料,咱们只要知道在 Android 中默许的列表惯性滑动便是根据此曲线算法完成的。

概念了解清楚后,再来看一下 splineBasedDecay 办法的界说:

fun <T> splineBasedDecay(density: Density): DecayAnimationSpec<T>

只要一个参数 density即屏幕像素密度。为什么要传 density 呢?这是由于 splineBasedDecay 是根据屏幕像素进行的动画速度衰减,当像素密度越大动画减速越快,动画的时长越短,动画惯性滑动的间隔越短;能够了解屏幕像素密度越大摩擦力越大,所以惯性滑动的间隔就越短。

运用 splineBasedDecay 完成动画作用,代码如下:

// 创立 Animatable 实例
val animatable = remember { Animatable(10.dp, Dp.VectorConverter) }
val scope = rememberCoroutineScope()
// 创立 splineBasedDecay
// 经过 LocalDensity.current 获取当时设备屏幕密度
val splineBasedDecay = splineBasedDecay<Dp>(LocalDensity.current)
Box(
    Modifier
        .padding(start = 10.dp, top = animatable.value)
        .size(100.dp, 100.dp)
        .background(Color.Blue)
        .clickable {
            scope.launch {
                // 启动衰减动画,初始速度设置为 1000.dp 每秒
                animatable.animateDecay(1000.dp, splineBasedDecay)
            }
        }
)

将上述代码分别在屏幕尺寸均为 6.0 英寸、屏幕密度分别为 440 dpi 和 320 dpi 的设备上运转,作用如下:

Android Compose 动画使用详解(九)Animatable之衰减动画

能够发现,屏幕密度小的动画运转的间隔更长。

rememberSplineBasedDecay

rememberSplineBasedDecaysplineBasedDecay 的作用是相同的,区别在 splineBasedDecay 上用 remember包裹了一层,上一节中运用 splineBasedDecay 并未用 remember包裹,就意味着每次界面刷新时都会重新调用 splineBasedDecay 创立衰减装备的实例。而运用 rememberSplineBasedDecay就能够优化该问题,且无需手动传入 density参数。

看一下 rememberSplineBasedDecay源码:

@Composable
actual fun <T> rememberSplineBasedDecay(): DecayAnimationSpec<T> {
    val density = LocalDensity.current
    return remember(density.density) {
        SplineBasedFloatDecayAnimationSpec(density).generateDecayAnimationSpec()
    }
}

首先也是经过 LocalDensity.current获取屏幕像素密度,然后运用 remember创立衰减装备实例,remember参数传入了 density,也便是当特殊情况屏幕密度发生变化时会重新创立衰减装备实例。

在开发中遇到要运用 splineBasedDecay的时分一般直接运用 rememberSplineBasedDecay 即可。

考虑:前面介绍 splineBasedDecay 是跟屏幕像素密度有关的,假如需求便是不想由于屏幕像素密度而导致不同设备表现不相同怎样办呢?或者动画作用的数值便是跟屏幕像素密度没关,比方作用于旋转视点的动画,此刻怎样办呢?这个时分就不能运用 splineBasedDecay,而是应该运用 exponentialDecay

exponentialDecay

exponentialDecay是指数衰减,即动画速度按指数递减,他不依赖屏幕像素密度,可用于通用数据的衰减动画。其界说如下:

fun <T> exponentialDecay(
    frictionMultiplier: Float = 1f,
    absVelocityThreshold: Float = 0.1f
): DecayAnimationSpec<T>

有两个参数,且都有默许值,参数解析如下:

  • frictionMultiplier:摩擦系数,摩擦系数越大,速度减速越快,反之则减速越慢
  • absVelocityThreshold:绝对速度阈值,当速度绝对值低于此值时动画中止,这儿的数值是指多少单位的速度,比方动画数值类型为 Dp,这儿传 100f 即 100f * 1.dp

运用如下:

 var move by remember { mutableStateOf(false) }
    val animatable = remember { Animatable(30.dp, Dp.VectorConverter) }
    val scope = rememberCoroutineScope()
    Box(
        Modifier
            .padding(start = 30.dp, top = animatable.value)
            .size(100.dp, 100.dp)
            .background(Color.Blue)
            .clickable {
                scope.launch {
                    // 运用 exponentialDecay 衰减动画
                    animatable.animateDecay(1000.dp, exponentialDecay())
                }
            }
    )

运转作用:

Android Compose 动画使用详解(九)Animatable之衰减动画

将摩擦系数设置为 5f 体验一下增加摩擦系数后的作用:

exponentialDecay(5f)

Android Compose 动画使用详解(九)Animatable之衰减动画

摩擦系数增大后,动画运转的间隔和时刻都显着缩短了。

将绝对速度阈值设置为 500f 再看一下作用:

exponentialDecay(absVelocityThreshold = 500f)

Android Compose 动画使用详解(九)Animatable之衰减动画

当动画速度达到阈值速度后动画就中止了,所以阈值越大动画越早中止。

实战

下面咱们用衰减动画完成一个转盘抽奖的动画作用,即当点击抽奖后转盘开端滚动然后慢慢停下,最终指针指向的方位便是中奖的奖品。

由于是旋滚动画,所以这儿咱们运用 exponentialDecay指数衰减动画,一起预备两张图片素材,如下:

Android Compose 动画使用详解(九)Animatable之衰减动画

Android Compose 动画使用详解(九)Animatable之衰减动画

将两张图片居中叠加,然后经过动画旋转下面的圆盘就完成了整个动画作用,代码如下:

// 创立动画实例
val animatable = remember { Animatable(0, Int.VectorConverter) }
// 获取协程作用域用户在按钮点击事情中敞开协程
val scope = rememberCoroutineScope()
// 中奖成果
var luckyResult by remember { mutableStateOf("") }
// 中奖项
val luckyItem = remember { arrayOf("50元红包", "20元红包","10元红包","100-50券","小米蓝牙耳机","谢谢参与") }
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
    Box{
        // 底部圆盘图片
        Image(
            painter = painterResource(R.drawable.bg),
            contentDescription = "bg",
            // 旋转视点设置为动画的值
            modifier = Modifier.rotate(animatable.value.toFloat())
        )
        // 中间指针图片
        Image(
            painter = painterResource(R.drawable.center),
            contentDescription = "center",
            // 设置点击事情
            modifier = Modifier.clickable(indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = {
                // 敞开协程
                scope.launch {
                    // 更新抽奖状况
                    luckyResult = "抽奖中"
                    // 敞开动画
                    // 初始速度设置为 10000 再加上 1000~10000 的随机数
                    // 衰减曲线设置为 exponentialDecay  摩擦系数设置为 0.5f
                    val result = animatable.animateDecay(10000 + Random.nextInt(1000,10000), exponentialDecay(frictionMultiplier = 0.5f))
                    // 动画履行完后从动画成果中获取最终的值,即旋转视点
                    val angle = result.endState.value
                    // 经过核算获取当时指针在哪个范围
                    val index = angle % 360 / 60
                    // 获取中奖成果,并显现在屏幕上
                    luckyResult = luckyItem[index]
                }
            })
        )
    }
    // 显现中奖成果
    Text(luckyResult, modifier = Modifier.padding(10.dp))
    // 增加重置按钮
    Button(onClick = {
        scope.launch {
            // 经过 snapTo 瞬间回到初始状况
            animatable.snapTo(0)
        }
    }){
        Text("重置")
    }
}

最终作用:

最终

本篇继 AnimatableanimateTosnapTo后继续介绍了 animateDecay 衰减动画的运用,包括怎样设置衰减曲线,不同衰减曲线的参数装备以及运用场景,并经过衰减动画完成了抽奖转盘作用。下一篇咱们继续探究 Animatable 的鸿沟设置及其相关的运用,请继续重视本专栏了解更多 Compose 动画内容。