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 …...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...

【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...