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 …...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
day36-多路IO复用
一、基本概念 (服务器多客户端模型) 定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用:应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...
