文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android Compose衰减动画Animatable怎么使用

2023-07-04 16:57

关注

这篇文章主要讲解了“Android Compose衰减动画Animatable怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android Compose衰减动画Animatable怎么使用”吧!

animateDecay

首先还是来看一下 animateDecay的定义:

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

跟前面介绍的 animateTo和 snapTo一样都是 suspend修饰的方法,即必须在协程中调用,参数有三个,分别解析如下:

返回值跟 animateTo一样都是 AnimationResult类型。

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

而衰减曲线的配置就是第二个参数 animationSpec,需要注意的是这里的 animationSpec 是 DecayAnimationSpec类型,它并不是前面介绍的 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 提供三个方法用于创建,分别是 splineBasedDecayrememberSplineBasedDecay和 exponentialDecay,那么这三种方法又有什么区别呢?下面分别对其进行详细介绍。

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 的设备上运行

可以发现,屏幕密度小的动画运行的距离更长。

rememberSplineBasedDecay

rememberSplineBasedDecay 跟 splineBasedDecay 的作用是一样的,区别在 splineBasedDecay 上用 remember包裹了一层,上一节中使用 splineBasedDecay 并未用 remember包裹,就意味着每次界面刷新时都会重新调用 splineBasedDecay 创建衰减配置的实例。而使用 rememberSplineBasedDecay就可以优化该问题,且无需手动传入 density参数。

看一下 rememberSplineBasedDecay源码:

@Composableactual 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>

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

使用如下:

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

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

exponentialDecay(5f)

摩擦系数增大后,动画运行的距离和时间都明显缩短了。

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

exponentialDecay(absVelocityThreshold = 500f)

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

实战

下面我们用衰减动画实现一个转盘抽奖的动画效果,即当点击抽奖后转盘开始转动然后缓缓停下,最后指针指向的位置就是中奖的奖品。

因为是旋转动画,所以这里我们使用 exponentialDecay指数衰减动画

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

// 创建动画实例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("重置")    }}

感谢各位的阅读,以上就是“Android Compose衰减动画Animatable怎么使用”的内容了,经过本文的学习后,相信大家对Android Compose衰减动画Animatable怎么使用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯