Android Compose衰减动画Animatable使用详解

目录
  • 前言
  • animateDecay
    • splineBasedDecay
    • rememberSplineBasedDecay
    • exponentialDecay
  • 实战
  • 最后

前言

之前介绍了 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 的设备上运行,效果如下:

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

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

运行效果:

将摩擦系数设置为 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("重置")
    }
}

最终效果:

最后

本篇继 AnimatableanimateTosnapTo后继续介绍了 animateDecay 衰减动画的使用,包括如何设置衰减曲线,不同衰减曲线的参数配置以及使用场景,并通过衰减动画实现了抽奖转盘效果。下一篇我们继续探索 Animatable 的边界设置及其相关的应用,请持续关注本专栏了解更多 Compose 动画内容。

以上就是Android Compose衰减动画Animatable使用详解的详细内容,更多关于Android Compose衰减动画的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android Compose 属性动画使用探索详解

    目录 前言 使用探索 ObjectAnimator 使用探索 ValueAnimator 使用探索 Compose 函数中使用属性动画 实战 上传开始动画 上传进度动画 上传完成动画 最后 前言 Jetpack Compose(简称 Compose )是 Google 官方推出的基于 Kotlin 语言的 Android 新一代 UI 开发框架,其采用声明式的 UI 编程特性使得 Android 应用界面的编写和维护变得更加简单. 本专栏将详细介绍在使用 Compose 进行 UI 开发中如何实

  • Android开发Compose框架使用开篇

    目录 Compose的诞生 Compose好处 Compose 架构 @Composable的背后 智能重组真的那么智能吗 最后 Compose的诞生 在2019年的谷歌IO大会上,Compose作为Android新一代UI开发亮相,因为声明式开发越来越流行了,对标IOS开发SwiftUi,Compose的立项也为Android开发新加了声明式ui的开发选项,在2021年7月1.0正式版本的诞生,也意味着Compose即将进入生产环节,国际app巨头Twitter就首当其冲,在新页面上用上了Co

  • Android开发Compose remember原理解析

    目录 正文 随机色文本 原因分析 正确实现 remember的原理剖析 小结 正文 看过Compose案例或者源码的你,相信肯定是见过 remember 了的.顾名思义,Compose是要让我们的代码“记住”东西,那到底是记住什么呢?要是不 remember,相关功能就实现不了了吗? 带着这些问题,来一探究竟吧 随机色文本 假设有这么一个“随机底色文本”的需求:实现一个 Text,其背景色每次启动都随机产生,且生命周期内不变 用Compose可以实现如下: private val items =

  • Android开发Jetpack Compose元素Modifier特性详解

    目录 正文 有序性 不可变性 正文 本文将会介绍Jetpack Compose中的Modifier.在谷歌官方文档中它的描述是这么一句话:Modifier元素是一个有序.不可变的集合,它可以往Jetpack Compose UI元素中添加修饰或者各种行为.例如,背景.填充和单击事件监听器装饰或添加行为到文本或按钮.本文将会从修饰符的两个特性有序和不可变入手来探究修饰符的应用,以下是本文目录: 有序性 不可变性 有序性 官方对修饰符定义的这个特性包含两个层面的意思,一是修饰符的使用是链式的它是有先

  • Android动效Compose贝塞尔曲线动画规格详解

    目录 正文 贝塞尔曲线 解析动画曲线 曲线源码分析 总结 正文 写Compose动画的时候使用animateXAsState的时候会注意到一个参数——animationSpec,如下: val borderRadius by animateIntAsState( targetValue = if (isRound) 100 else 0, animationSpec = tween( durationMillis = 3000, easing = LinearEasing ) ) 此处就不深入探

  • Android Jetpack Compose开发实用小技巧

    目录 前言 实用小技巧 如何移除View点击阴影 Text文本如何垂直居中 如何移除Button的点击阴影 Dialog宽度如何全屏 如何提升编码效率 前言 在Compose开发的过程中,我们会经常遇到一些看起来很简单却不知道如何处理的小问题,比如去除点击阴影.Dialog全屏等问题,本文记录了这些常见小问题的处理方式.如有更好方案欢迎大佬们交流探讨- 实用小技巧 如何移除View点击阴影 这里的View指的是除了Button系列的之外,如Button.TextButton等,也就是自身没有on

  • Android Compose衰减动画Animatable使用详解

    目录 前言 animateDecay splineBasedDecay rememberSplineBasedDecay exponentialDecay 实战 最后 前言 之前介绍了 Animatable 动画以及其 animateTo和 snapTo两个开启动画 api 的使用,实际上 Animatable 除了这两个 api 以外还有一个 animateDecay即本篇要介绍的衰减动画. 什么是衰减动画呢?就是动画速度由快到慢最后停止,最常见的应用场景就是惯性动画,比如滑动列表时手指松开后

  • Android 逐帧动画创建实例详解

    Android 逐帧动画创建实例详解 前言: 我们看早期电影的时候,电影通常是一张一张播放,用我们现在专有名词来说,就是一帧帧来,安卓同样有这样动画效果的编排形式. 那么我们先定义逐帧动画xml文件 <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" an

  • Android Compose状态改变动画animateXxxAsState使用详解

    目录 前言 animateXxxAsState 基础使用 动画监听 使用示例 animateFloatAsState animateIntAsState animateColorAsState animateSizeAsState/animateIntSizeAsState animateOffsetAsState/animateIntOffsetAsState animateRectAsState 实战 最后 前言 上一篇文章我们探索了 Compose 中属性动画的使用,发现属性动画确实是可以

  • Android 帧动画的实例详解

    Android 帧动画的实例详解 对于 Android 帧动画 大体上可以理解成 一张张图片 按一定顺序切换, 这样当连续几张图是一组动画时,就可以连起来了看成是一个小电影,你懂得 好得,比就装到这里,下面开始进入正题,由于产品需求 需要做一个 声音喇叭动态切换的样式,我特么第一就想到是帧动画切换,然后就百度了一些资料,发现 真的, 现在这个网上太多的资料是 copy粘贴过来的, 一错全错,对于这种情况我只想说,made,一群垃圾, 所以今天我将带你们走进Android 正确帧动画地址. 第一步

  • Android Flutter实现3D动画效果示例详解

    目录 前言 AnimatedWidget 简介 3D 旋转动画的实现 总结 前言 上一篇我们介绍了 Animation 和 AnimationController 的使用,这是最基本的动画构建类.但是,如果我们想构建一个可复用的动画组件,通过外部参数来控制其动画效果的时候,上一篇的方法就不太合适了.在 Flutter 中提供了 AnimatedWidget 组件用于构建可复用的动画组件.本篇我们用 AnimatedWidget 来实现组件的3D 旋转效果,如下图所示. AnimatedWidge

  • Android 中 Tweened animation的实例详解

    Android 中 Tweened animation的实例详解 Tweened animation有四种类型,下面主要介绍Scale类型. 运行效果如下: Android SDK提供了2种方法:直接从XML资源中读取Animation,使用Animation子类的构造函数来初始化Animation对象,第二种方法在看了Android SDK中各个类的说明就知道如何使用了,下面简要说明从XML资源中读取Animation.XML资源中的动画文件animation.xml内容为: <?xml ve

  • Android activity堆栈及管理实例详解

    本示例演示如何通过设置Intent对象的标记,来改变当前任务堆栈中既存的Activity的顺序. 1. Intent对象的Activity启动标记说明: FLAG_ACTIVITY_BROUGHT_TO_FRONT 应用程序代码中通常不设置这个标记,而是由系统给单任务启动模式的Activity的设置. FLAG_ACTIVITY_CLEAR_TASK 如果给Intent对象添加了这个标记,那么在Activity被启动之前,会导致跟这个Activity关联的任何既存的任务都被清除.也就是说新的Ac

  • Android Canvas方法总结最全面详解API(小结)

    本篇文章主要介绍了Android Canvas方法总结最全面详解API,分享给大家,具体如下: 常用方法 drawXxx方法族:以一定的坐标值在当前画图区域画图,另外图层会叠加, 即后面绘画的图层会覆盖前面绘画的图层. clipXXX方法族:在当前的画图区域裁剪(clip)出一个新的画图区域,这个 画图区域就是canvas对象的当前画图区域了.比如:clipRect(new Rect()), 那么该矩形区域就是canvas的当前画图区域 getXxx方法族:获得与Canvas相关一些值,比如宽高

  • Android自定义View实现体重表盘详解流程

    目录 效果视频 分析 起始角度 圆弧 指针 代码 初始化属性 画布 绘制内圆弧 绘制外圆弧 绘制中间指针 绘制中间文字 绘制左右两边文字 动画 全部代码 下载链接 效果视频 分析 起始角度 如下图所示,起点角度为150,终点角度为240 圆弧 白色圆弧为整个圆弧范围,蓝色圆弧为根据数据变动而覆盖白色圆弧,蓝色圆弧比白色圆弧大一点,突出显示 InnerArcPaint.setStrokeWidth( Width * (float)0.1 ); OuterArcPaint.setStrokeWidt

  • Android View的事件体系教程详解

    目录 一.什么是View?什么是ViewGroup? 二.View的位置 三.View的触摸事件 1.MotionEvent 2.TouchSlop 3.VelocityTracker 5.Scroller 四.View的滑动 1)使用Scroll 2)通过动画 3)使用延时策略 五.View的事件分发机制 六.View的滑动冲突问题 View的滑动冲突常见可以简单分为三种: 滑动冲突的处理规则 滑动冲突的解决方法 一.什么是View?什么是ViewGroup? View是Android中所有控

随机推荐