Compose 动画 (七) : 高可定制性的动画 Animatable
1. Animatable和animateDpAsState的区别是什么
Animatable是Android Compose动画的底层API,如果我们查看源码,可以发现animateDpAsState内部是调用的animateValueAsState,而animateValueAsState内部调用的是Animatable
animateDpAsState比Animatable更便捷易用,但屏蔽了部分功能,animateDpAsState抛弃了设置初始值的功能。
animateDpAsState是对于动画具体场景化的一种实现,它针对状态切换这一种动画的场景,设置了专门的扩展,而状态切换是不需要初始值的。
如果想要更高的可定制性(比如设置初始值),那么就使用Animatable
2. animateDpAsState实现动画
首先,我们使用animateDpAsState来实现一个简单的动画
var big by remember {mutableStateOf(false)
}
val anim = animateDpAsState(if (big) 100.dp else 50.dp)
Box(modifier = Modifier.size(anim.value).background(Color.Blue).clickable { big = !big })

3. 使用Animatable实现上述动画
var big by remember {mutableStateOf(false)
}
val size = remember(big) {if (big) 100.dp else 50.dp
}
val anim1 = remember { Animatable(size, Dp.VectorConverter) }
LaunchedEffect(key1 = big, block = {anim1.animateTo(size)
})
Box(modifier = Modifier.size(anim1.value).background(Color.Blue).clickable { big = !big })
可以发现效果是一样的

这里需要注意的点有
Dp.VectorConverter: 是TwoWayConverter<T, V>类型,用来进行数值转换LaunchedEffect: 是Compose中的协程,animateTo是一个挂起函数,需要在协程中使用
接下来我们来逐个讲解
3.1 TwoWayConverter
Animatable中默认的TwoWayConverter是Float.VectorConverter
fun Animatable(initialValue: Float,visibilityThreshold: Float = Spring.DefaultDisplacementThreshold
) = Animatable(initialValue,Float.VectorConverter,visibilityThreshold
)
可以看到Float.VectorConverter是TwoWayConverter<Float, AnimationVector1D>类型,说明是直接转换成Float的
val Float.Companion.VectorConverter: TwoWayConverter<Float, AnimationVector1D>get() = FloatToVectorprivate val FloatToVector: TwoWayConverter<Float, AnimationVector1D> =TwoWayConverter({ AnimationVector1D(it) }, { it.value })
3.2 Animatable 支持 多种转换
Animatable不仅支持Float,还支持多种转换
//Float (默认不传就是Float类型)
val animatableFloat1 = remember { Animatable(100F) }
val animatableFloat2 = remember { Animatable(100F,Float.VectorConverter) }
//DP
val animatableDp = remember{ Animatable(100.dp,Dp.VectorConverter) }
//Int
val animatableInt = remember { Animatable(100, Int.VectorConverter) }
//Rect
val animatableRect =remember { Animatable(Rect(100F, 100F, 100F, 100F), Rect.VectorConverter) }
//Offset
val animatableOffset = remember {Animatable(Offset(100F, 100F), Offset.VectorConverter)
}
//IntOffset
val animatableIntOffset = remember {Animatable(IntOffset(100, 100), IntOffset.VectorConverter)
}
//Size
val animatableSize = remember {Animatable(Size(100F, 100F), Size.VectorConverter)
}
//IntSize
val animatableIntSize = remember {Animatable(IntSize(100, 100), IntSize.VectorConverter)
}val blackColor = Color(0xFF000000)
val converter = remember(blackColor.colorSpace) {(Color.VectorConverter)(blackColor.colorSpace)
}
//Color
val animatableColor = remember {Animatable(blackColor, converter)
}
3.3 1D、2D、3D和4D的区别
val animatableDp = remember{ Animatable(100.dp,Dp.VectorConverter) }
我们以Dp.VectorConverter为例,可以发现AnimationVector1D后缀带有1D字样,这是啥意思呢?
val Dp.Companion.VectorConverter: TwoWayConverter<Dp, AnimationVector1D>get() = DpToVector
D代表的是维度,即dimension的意思。1D、2D、3D、4D分别代表着一维、二维、三维、四维
val animatableOffset = remember {Animatable(Offset(100F, 100F), Offset.VectorConverter)
}
比如这个Offset就是二维的
val Offset.Companion.VectorConverter: TwoWayConverter<Offset, AnimationVector2D>get() = OffsetToVector
4. Compose中的协程
Animatable中的animateTo是一个挂起函数,需要在协程中使用。
4.1 之前协程的写法
lifecycleScope.launch {//TODO
}
4.2 LaunchedEffect
但是在Compose里面,不能这么写,因为这种用法,没有针对Compose做优化,在Compose重组过程中,会重复进行调用。
所以Compose提供了一个专门在Compose中启动协程的API : LaunchedEffect
LaunchedEffect(key1 = 123, block = {//代码块anim1.animateTo(100.dp)
})
key不变的话可以这么写 : LaunchedEffect(Unit, block = {})
这里,只要key没有发生变化,那么block代码块就不会再次执行。这里也可以有多个key
LaunchedEffect(key1 = 123,key2=456,key3=789, block = {})
如果有让LaunchedEffect多次执行的场景,那么key1参数就需要可以变化
到这里,我们也就能看懂 3. 使用Animatable实现上述动画 中的代码了
var big by remember {mutableStateOf(false)
}
val size = remember(big) {if (big) 100.dp else 50.dp
}
val anim1 = remember { Animatable(size, Dp.VectorConverter) }
LaunchedEffect(key1 = big, block = {anim1.animateTo(size)
})
Box(modifier = Modifier.size(anim1.value).background(Color.Blue).clickable { big = !big })
5. 使用Animatable进行流程定制
Animatable对于动画是高度可定制化的,我们可以通过Animatable来进行动画流程的定制
var big by remember {mutableStateOf(false)}val size = remember(big) {if (big) 100.dp else 50.dp}val anim1 = remember { Animatable(size, Dp.VectorConverter) }LaunchedEffect(key1 = big, block = {//流程定制anim1.animateTo(10.dp)anim1.animateTo(200.dp)anim1.animateTo(size)})Box(modifier = Modifier.size(anim1.value).background(Color.Blue).clickable { big = !big })
比如这里会先动画执行到10.dp,然后再执行到200.dp,最后再执行目标的size
效果如下所示

6. snapTo 瞬间完成动画
snapTo会瞬间完成动画,就和没有动画的效果进行变化是一样的。
那这个方法有啥用呢 ?
可以在开始动画前先瞬间设置好初始动画状态,从而达到设置动画初始值的效果。
var big by remember {mutableStateOf(false)
}
val size = remember(big) {if (big) 100.dp else 50.dp
}
val anim1 = remember { Animatable(size, Dp.VectorConverter) }
LaunchedEffect(key1 = big, block = {//代码块anim1.snapTo(size) //瞬间完成 ---> 设置初始值
})Box(modifier = Modifier.size(anim1.value).background(Color.Blue).clickable { big = !big })
效果如下所示

7. animateDecay 惯性衰减动画
animateDecay是惯性衰减动画,比如惯性滑动操作,可以实现和Android自带的惯性滑动一致的效果。
那和animateTo有啥区别呢 ?
最大的区别在于 animateTo 是需要设置目标值的,也就是动画结束的那一刻 某个view属性的值 必须明确指定,
而惯性衰减动画 animateDecay 则不需要指定。
animateDecay: 从初始速度慢慢停下来,例如松手之后的惯性滑动animateTo: 指定结束的属性值
val anim = remember {Animatable(0.dp, Dp.VectorConverter)
}
val decay = remember {exponentialDecay<Dp>()
}
LaunchedEffect(key1 = Unit, block = {delay(1000L)anim.animateDecay(2000.dp, decay) {//动画监听,可以获取动画当前的值 this.value}
})
Box(modifier = Modifier.padding(0.dp, anim.value, 0.dp, 0.dp).size(50.dp).background(Color.Blue)
)
效果如下所示

7.1 两种decay的区别
exponentialDecay和rememberSplineBasedDecay都可以实现惯性衰减动画,但它们两个有什么区别呢 ?
splineBasedDecay: 一般情况下只有在使用像素的情况下,会使用这个,它不会做针对像素做修正的,多个设备滑动效果会不一致exponentialDecay:其他情况下一般都是用这个,不会根据像素密度变化而变化,比如DP,颜色,角度之类的
7.2 为什么rememberSplineBasedDecay自带remember
我们可以发现,rememberSplineBasedDecay是自带remember的,但是exponentialDecay却没有自带remember,为什么会这么设计呢 ? 我们来分别看一下rememberSplineBasedDecay的源码
rememberSplineBasedDecay
@Composable
actual fun <T> rememberSplineBasedDecay(): DecayAnimationSpec<T> {val density = LocalDensity.currentreturn remember(density.density) {SplineBasedFloatDecayAnimationSpec(density).generateDecayAnimationSpec()}
}
可以看到remember中传入了density这个参数,说明rememberSplineBasedDecay会随着density像素密度的改变而改变。
而 exponentialDecay 则因为不会响应系统的变化,所以不需要在remember中传入density,就由使用者自己来包装remember就好了。
7.3 animateDecay中的block监听
suspend fun animateDecay(initialVelocity: T,animationSpec: DecayAnimationSpec<T>,block: (Animatable<T, V>.() -> Unit)? = null): AnimationResult<T, V>
animateDecay中有个block的监听,动画发生变化的时候,会回调这个监听。
通过this.value能够取到当前的动画值。
我们可以通过这个block回调,来让其他view 响应这个动画的变化。
LaunchedEffect(key1 = Unit, block = {anim.animateDecay(2000.dp, decay) {//动画监听,可以获取动画当前的值 this.value}
})
8. Compose 动画系列
Compose 动画系列,后续持续更新
Compose 动画 (一) : animateXxxAsState 实现放大/缩小/渐变等效果
Compose 动画 (二) : 为什么animateDpAsState要用val ? MutableState和State有什么区别 ?
Compose 动画 (三) : AnimatedVisibility 从入门到深入
Compose 动画 (四) : AnimatedVisibility 各种入场和出场动画效果
Compose 动画 (五) : animateContentSize / animateEnterExit / Crossfade / AnimatedContent
Compose 动画 (六) : 使用Transition管理多个动画,实现动画预览
相关文章:
Compose 动画 (七) : 高可定制性的动画 Animatable
1. Animatable和animateDpAsState的区别是什么 Animatable是Android Compose动画的底层API,如果我们查看源码,可以发现animateDpAsState内部是调用的animateValueAsState,而animateValueAsState内部调用的是Animatable animateDpAsState比A…...
vue3组件传值
1.父向子传值 父组件 引入子组件 import Son from ./components/Son.vue 设置响应式数据 const num ref(99) 绑定到子组件 <Son :num"num"></Son> 子组件 引入defineProps import { defineProps } from vue; 生成实例接收数据 type设置接收类…...
小白开发微信小程序00--文章目录
一个小白,一个老牛,空手能不能套白羊,能不能白嫖?我告诉你,一切都so easy,这个系列从0到106,屌到上天,盖过任何一个,试问,网上讲微信小程序开发的,…...
随手记录第九话 -- Java框架整合篇
框架莫过于Spring了,那就以它为起点吧。 本文只为整理复习用,详细内容自行翻看以前文章。 1.Spring 有人说是Spring成就Java,其实也不是并无道理。 1.1 Spring之IOC控制反转 以XML注入bean的方式为入口,定位、加载、注册&…...
电影《铃芽之旅》观后感
这周看了电影《铃芽之旅》,整部电影是新海诚的新作。电影讲述的是女主铃芽为了关闭往门,在日本旅行中,遭遇灾难的故事。 (1)往昔记忆-往昔之物 电影中,有很多的“往门”,换成中国的话说…...
Web自动化测试(二)(全网最给力自动化教程)
欢迎您来阅读和练手!您将会从本章的详细讲解中,获取很大的收获!开始学习吧! 2.4 CSS定位2.5 SeleniumBuilder辅助定位元素2.6 操作元素(键盘和鼠标事件) 正文 2.4 CSS定位 前言 大部分人在使用selenium定…...
【C语言经典例题!】逆序字符串
目录 一、题目要求 二、解题步骤 ①递归解法 思路 完整代码 ②循环解法 思路 完整代码 嗨大家好! 本篇博客中的这道例题,是我自己在一次考试中写错的一道题 这篇博客包含了这道题的几种解法,以及一些我自己对这道题的看法ÿ…...
21 - 二叉树(三)
文章目录1. 二叉树的镜像2. 判断是不是完全二叉树3. 完全二叉树的节点个数4. 判断是不是平衡二叉树1. 二叉树的镜像 #include <ctime> class Solution {public:TreeNode* Mirror(TreeNode* pRoot) {// write code hereif (pRoot nullptr) return pRoot;//这里记得要记得…...
【A-Star算法】【学习笔记】【附GitHub一个示例代码】
文章目录一、算法简介二、应用场景三、示例代码Reference本文暂学习四方向搜索,一、算法简介 一个比较经典的路径规划的算法 相关路径搜索算法: 广度优先遍历(BFC)深度优先遍历(DFC)Di jkstra算法&#…...
纽扣电池澳大利亚认证的更新要求
澳大利亚强制性安全和信息标准草案具体规定了对含有纽扣电池和纽扣电池以 及纽扣电池和纽扣电池本身的消费品的要求, 适用范围 1.本法规适用于: 纽扣锂电池(任何尺寸和类型); 直径为16毫米或以上的纽扣锂电池: 一起提供的纽扣电池(未预先安装在产品中)。 2.但是&…...
零代码零距离,明道云开放日北京站圆满结束
文/麦壁瑜 编辑/李雨珂 2023年3月17日,为期一天的明道云开放日北京站圆满结束。本次开放日迎来超过100名伙伴和客户现场参会,其中不乏安利、通用技术集团、民生银行、迈外迪、DELSK集团、中国人民养老保险、北京汽车等知名企业代表。北京大兴机场、作业…...
第五章Vue路由
文章目录相关理解vue-router的理解对SPA应用的理解路由的理解基本路由几个注意点嵌套路由——多级路由路由query参数命名路由路由的params参数路由的props配置路由跳转的replace方法编程式路由导航缓存路由组件路由组件独有的生命钩子activated和deactivated路由守卫全局路由守…...
Git常用指令
Git是什么: Git是分布式版本控制系统(Distributed Version Control System,简称 DVCS),分为两种类型的仓库: 本地仓库和远程仓库 第一步先新建仓库,本地 init ,然后提交分枝 链接仓库…...
Java每日一练(20230329)
目录 1. 环形链表 II 🌟🌟 2. 基础语句 ※ 3. 最小覆盖子串 🌟🌟🌟 🌟 每日一练刷题专栏 🌟 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 环形…...
【面试题】JS的一些优雅写法 reduce和map
大厂面试题分享 面试题库 前后端面试题库 (面试必备) 推荐:★★★★★ 地址:前端面试题库 web前端面试题库 VS java后端面试题库大全 JS的一些优雅写法 reduce 1、可以使用 reduce 方法来实现对象数组中根据某一key值求和 …...
【蓝桥杯真题】包子凑数(裴蜀定理、动态规划、背包问题)
题意 小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼,可以认为是无限笼。 每当有顾客想买X个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干…...
一种免费将PDF转word的方式
pdf转word的需求对我来说很重要,我经常会有PDF转word的方式,但是网上搜索到的方式,要么收费、要么限制pdf大小或者限制转换次数。这里我分享一种免费转换的方式:用Acrobat Pro 来做转换。Adobe Acrobat Pro拥有强大的功能…...
MyBatis-面试题
文章目录1.什么是MyBatis?2.#{}和${}的区别是什么?3.MyBatis的一级、二级缓存4.MyBatis的优缺点5.当实体类中的属性名和表中的字段名不一样 ,怎么办 ?6.模糊查询like语句该怎么写?7.Mybatis是如何进行分页的?分页插件的原理是什…...
jQuery一些问题和ajax操作
jQuery语法: 文档就绪事件:文档加载之后运行jQuery代码,相当于jQuery的入口函数。 $(document).ready(function(){// 开始写 jQuery 代码...}); 简写: $(function(){// 开始写 jQuery 代码...}); jQuery选择器: …...
Pytorch构建自己的数据集
1.Pytorch内置的Dataset Pytorch中内置了许多数据集,我们可以从torchvision库中进行导入。比如,我们可以导入Fashion-MNIST数据集 import torch from torch.utils.data import Dataset from torchvision import datasets from torchvision.transforms …...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
