手把手教你写 Compose 动画 -- 状态转移型动画 API:animate*AsState()

Jetpack Compose 提供了一系列功能强大且可扩展的 API,可用于在应用界面中轻松实现各种动画效果。这一系列文章会逐个介绍所有的动画 API,通过最直观的 Demo 示例,手把手教你怎么写动画以及带你了解动画背后的原理。


📑 手把手教你写 Compose 动画 - - 状态转移型动画 API:animate*AsState()

📑 手把手教你写 Compose 动画 - - 流程定制型动画 API:Animatable()

📑 手把手教你写 Compose 动画 - - 讲的不能再细的 AnimationSpec 动画规范

📑 手把手教你写 Compose 动画 - - 过渡动画 API:Transition

📑 手把手教你写 Compose 动画 - - 显示与消失 API:AnimatedVisibility

📑 手把手教你写 Compose 动画 - - 简单页面切换动画 API:Crossfade

📑 手把手教你写 Compose 动画 - - 更强大的多组件切换动画 API:AnimatedContent

📑 手把手教你写 Compose 动画 - - 组件大小变化 API:animateContentSize



📓 动画图表


在每一篇文章开头,我都会放一张 Compose 动画 API 的图表,以便你有最直观的感受。

在这里插入图片描述


📓 animate*AsState


animate*AsState 函数应该算是 Compose 中最简单的动画 API,用于为单个值添加动画效果。只需提供结束值(或目标值),该 API 就会从当前值开始向指定值播放动画。

一直以来我都认为:探索新技术的最佳方式就是尝试它们,所以我们先构建一个简单场景:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Column{Image(painter = painterResource(R.drawable.bicycle),contentDescription = null,modifier = Modifier.height(90.dp).absoluteOffset(x = 0.dp))Button(onClick = {},modifier = Modifier.fillMaxWidth().wrapContentSize(align = Alignment.Center)) {Text(text = "Ride")}}}}
}

这是一个极其简单的场景:一个图片,一个 Button,初始效果如下:

在这里插入图片描述

现在我们让小刺猬动起来(从左到右),代码可以这样写:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {var offset by remember { mutableStateOf(0.dp) }  // 定义偏移变量Column{Image(painter = painterResource(R.drawable.bicycle),contentDescription = null,modifier = Modifier.height(90.dp).absoluteOffset(x = offset)  // 获取偏移值)Button(onClick = { offset = 360.dp },  // 修改偏移值modifier = Modifier.fillMaxWidth().wrapContentSize(align = Alignment.Center)) {Text(text = "Ride")}}}}
}

如注释说明,我只修改了三个地方(理解起来没问题吧?),现在来看下效果:

在这里插入图片描述

小刺猬动起来了,但是这种效果给人的感觉就很生硬,完全称不上是动画,而是“瞬间移动”。

现在我们可以开始尝试用 animate*AsState 改善动画效果,写法很简单:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {// var offset by remember { mutableStateOf(0.dp) }  // 定义偏移变量var offset by remember { animateDpAsState(0.dp) }   // 替换为 animateDpAsStateColumn{Image(painter = painterResource(R.drawable.bicycle),contentDescription = null,modifier = Modifier.height(90.dp).absoluteOffset(x = offset)  // 获取偏移值)Button(onClick = { offset = 360.dp },  // 修改偏移值modifier = Modifier.fillMaxWidth().wrapContentSize(align = Alignment.Center)) {Text(text = "Ride")}}}}
}

如你所见,我仅仅用 animateDpAsState 替换 mutableStateOf 后,就可以对数值的大小实现渐变的调整,但很遗憾,这些直接替换是有红线报错的。

在这里插入图片描述

接下来我们一起修复这个报错,最终推导出 animateDpAsState 的正确写法。

  1. 首先我们回顾下 remember 的作用:它是防止变量被多次重复初始化的,而 animateDpAsState 天生自带这个能力(它的源码内部是包了 remember 的),所以这里我们可以去掉 remember。

在这里插入图片描述

  1. 再来回顾下 by 的作用:把左边 offset 变量委托给右边的 mutableStateOf,mutableStateOf 提供了读和写的功能,但是有一个细节需要注意了,我们对比下 mutableStateOfanimateDpAsState 函数的定义:
fun <T> mutableStateOf(value: T,policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T> = createSnapshotMutableState(value, policy)
@Composable
fun animateDpAsState(targetValue: Dp,animationSpec: AnimationSpec<Dp> = dpDefaultSpring,label: String = "DpAnimation",finishedListener: ((Dp) -> Unit)? = null
): State<Dp> {

⇒ mutableStateOf 返回的是一个 MutableState 对象

⇒ animateDpAsState 返回的是一个 State 对象

在 Compose 中,State 对象只提供读的功能,你是没办法写的!但例子中 var 就代表 offset 是可写的,这就冲突了。Android Studio 的报错提示其实已经说明了:

在这里插入图片描述

所以现在我们把 var 改成 val

在这里插入图片描述

这里特殊说明一下:

⇒ 蓝色地方有波浪线是因为随着官方 API 的更新,不带 label 参数的 animateDpAsState 函数被弃用了。

@Deprecated("animate*AsState APIs now have a new label parameter added.",level = DeprecationLevel.HIDDEN
)
@Composable
fun animateDpAsState(targetValue: Dp,animationSpec: AnimationSpec<Dp> = dpDefaultSpring,finishedListener: ((Dp) -> Unit)? = null
)

官方建议我们使用带 label 参数的 animateDpAsState,所以如果你就不想加,也不会影响程序运行(至于为什么官方这么强烈建议加上这个 label 标签,会在别的动画文章里面说明)。

val offset by animateDpAsState(0.dp, label = "")

⇒ 红色地方报错的原因是显而易见的,因为 offset 不可以手动写,那该怎么修改 offset 值?

  1. 以下是正确的写法:
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)var bicycleStart by mutableStateOf(false)setContent {val offset by animateDpAsState(if (bicycleStart) 360.dp else 0.dp, label = "")Column{Image(painter = painterResource(R.drawable.bicycle),contentDescription = null,modifier = Modifier.height(90.dp).absoluteOffset(x = offset))Button(onClick = { bicycleStart = !bicycleStart },modifier = Modifier.fillMaxWidth().wrapContentSize(align = Alignment.Center)) {Text(text = "Ride")}}}}
}

既然我没法在别的地方手动修改值,让就通过改变另外一个 bicycleStart 状态,引发重组,从而改变 animateDpAsState 内部的值。

到这里程序就没有任何错误了,这也是 animateDpAsState 的正确写法

现在,运行下看看效果:(对比 mutableStateOf 和 animateDpAsState)

在这里插入图片描述

效果是很明显的,起码我们能够看出来车是开起来了,而不是瞬间移动。


除了 Dp,Compose 为 Float、Color、Size、Offset、Rect、Int、IntOffset 和 IntSize 都提供了开箱即用的 animate*AsState 函数,用法差不多,不再过多举例了。

在这里插入图片描述

至此,Animate*AsState() 的基本用法了解完了,就这么简单,但是我们还得细看下这个函数的定义:

@Composable
fun animateDpAsState(targetValue: Dp,animationSpec: AnimationSpec<Dp> = dpDefaultSpring,label: String = "DpAnimation",finishedListener: ((Dp) -> Unit)? = null
)

它还有一个核心参数:animationSpec,它是一个 AnimationSpec 类型的,你可以通过可选的 AnimationSpec 参数来自定义动画规范(也就是可以实现不同类型的动画效果)。

AnimationSpec 的内容着实不少,作为单独的知识点放在 【 AnimationSpect 详解 】 一文细说。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/631580.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unreal Engine(UE5)中构建离线地图服务

1. 首先需要用到3个软件&#xff0c;Unreal Engine&#xff0c;gis office 和 bigemap离线服务器 Unreal Engine下载地址:点击前往下载页面 Gis office下载地址:点击前往下载页面 Bigemap离线服务器 下载地址: 点击前往下载页面 Unreal Engine用于数字孪生项目开发&#x…

4种方法用Python批量实现多Excel多Sheet合并

目录 方法一&#xff1a;使用pandas库 方法二&#xff1a;使用openpyxl库 方法三&#xff1a;使用xlrd和xlwt库 方法四&#xff1a;使用os和glob库 在数据处理中&#xff0c;经常需要将多个Excel文件中的多个工作表进行合并。以下介绍了4种方法&#xff0c;使用Python批量实…

js数组长度不够补0 且 够的话则截取到期望长度

当我们需要处理数组的长度不够的情况时&#xff0c;可以通过补0来完成&#xff1b;数组长度够的情况下&#xff0c;需要截取期望的长度。 在示例代码中&#xff0c;我们定义了一个函数padArrZeroSubLength 它接受两个参数&#xff1a;原始数组和期望的长度。 1&#xff09;函…

【Android】为什么在子线程中更新UI不会抛出异常

转载请注明来源&#xff1a;https://blog.csdn.net/devnn/article/details/135638486 前言 众所周知&#xff0c;Android App在子线程中是不允许更新UI的&#xff0c;否则会抛出异常&#xff1a; android.view.ViewRootImpl$CalledFromWrongThreadException: Only the origin…

【Ubuntu18.04安装Labelme】

Ubuntu18.04安装Labelme 1 安装Anaconda并创建conda环境2 安装依赖3 安装Labelme4 安装验证 1 安装Anaconda并创建conda环境 Anaconda3安装教程&#xff1a;https://blog.csdn.net/dally2/article/details/108206234 "ctrlaltt"快捷键打开终端&#xff0c;创建conda…

数据机房中智能小母线与列头柜方案的对比分析

0引言 近年来&#xff0c;我国信息技术行业发展十分迅猛&#xff0c;得益于国家政策的大力支持&#xff0c;政府金融、互联网公司、运营商等客户都在不断地新建和升级数据中心&#xff0c;以匹配其数据业务的增长速度。我国数据中心IT市场一直保持着连续快速增长的态势。国内数…

橘子学Mybatis07之Mybatis关于缓存的设计

很逆天的一件事是&#xff0c;我上一次发mybatis是在2022年10月15号&#xff0c;然后直到今天才开始总结下一篇Mybatis的东西。一年里面忙成那啥了&#xff0c;而且重心都投入在了Elasticsearch的学习上面&#xff0c;基本一年下来都在搞ES&#xff0c;并且考下了ECE认证&#…

【Poco库源码解析】Poco库中的通知

1、介绍 PocoPocoPoco 中的通知&#xff0c;是消息源通过中间载体将消息发送给观察者&#xff0c;通知可以分为 同步通知和异步通知。 下图是同步通知&#xff0c;消息发送流程&#xff1a; 2.同步通知 2.1 消息 class Notification: public RefCountedObject { public:ty…

生产环境LVM磁盘扩容

使用df -Th 命令查看磁盘信息 ,可以看到当前LVM逻辑卷容量是38G [rootZ ~]# df -TH 文件系统 类型 容量 已用 可用 已用% 挂载点 /dev/mapper/centos-root xfs 38G 2.4G 36G 7% / devtmpfs devtmpfs 1.1G 0 1.1G …

使用屏幕捕捉API:一站式解决屏幕录制需求

随着科技的发展&#xff0c;屏幕捕捉API技术逐渐成为一种热门的录屏方法。本文将详细介绍屏幕捕捉API技术的原理、应用场景以及如何利用这一技术为用户提供便捷、高效的录屏体验。 在线录屏 | 一个覆盖广泛主题工具的高效在线平台(amd794.com) https://amd794.com/recordscre…

win10系统的hiberfil.sys如何删除

C盘莫名其妙地出现了一个叫hiberfil.sys的文件。我一看&#xff0c;好家伙&#xff0c;6个多G&#xff0c;让我本就所剩无几的C盘空间再次雪上加霜&#xff01; 然后我就研究了一下。 hiberfil.sys是什么&#xff1f; 该文件用于将计算机处于休眠状态时的所有内容保存到硬盘…

Windows安装WSL2精简版教程

文章目录 一、安装WSL二、更改WSL的存放路径/备份WSL三、安装WSL Terminall四、WSL界面&#xff1a;xlaunch五、WSL1升级WSL2六、WSL2与VMware兼容问题七、更改手动导入的wsl的默认登录用户参考 一、安装WSL 步骤1 - 启用适用于 Linux 的 Windows 子系统&#xff1a; 需要先启…

.NET分库分表:高性能分页(mycat之外的选择)

&#x1f3c6;作者&#xff1a;科技、互联网行业优质创作者 &#x1f3c6;专注领域&#xff1a;.Net技术、软件架构、人工智能、数字化转型、DeveloperSharp、微服务、工业互联网、智能制造 &#x1f3c6;欢迎关注我&#xff08;Net数字智慧化基地&#xff09;&#xff0c;里面…

【USTC】verilog 习题练习 21-25

21 基于端口名称的实例化 题目描述 创建一 verilog 电路&#xff0c;实现对模块 mod_a 基于端口名称的实例化&#xff0c;如下图所示&#xff1a; 其中mod_a模块的代码为&#xff1a; module mod_a (output out1,output out2,input in1,input in2,input in3,in…

边缘计算AI智能分析网关V4客流统计算法的概述

客流量统计AI算法是一种基于人工智能技术的数据分析方法&#xff0c;通过机器学习、深度学习等算法&#xff0c;实现对客流量的实时监测和统计。该算法主要基于机器学习和计算机视觉技术&#xff0c;其基本流程包括图像采集、图像预处理、目标检测、目标跟踪和客流量统计等步骤…

【架构】docker实现3主3从架构配置【案例1/4】

一&#xff0c;集群规划及准备工作 架构实现&#xff1a;Redis3主3从 二&#xff0c;搭建命令 第一步&#xff0c;创建6台服务&#xff1a; docker run -d --name redis-node-1 --net host --privilegedtrue -v /data/redis/share/redis-node-1:/data redis:6.0.8 --clust…

基于Springboot+vue图书管理系统(前后端分离)

该项目完全免费 项目技术栈前后端分离&#xff1a; 后端&#xff1a;Springboot Mybatis-plus 前端&#xff1a;Vue ElementUI 数据库&#xff1a; MySQL 项目功能描述 管理员&#xff1a; 登录、个人信息、修改密码、管理后台管理系统所有数据 首页统计&#xff1a;…

Python(37):使用logging的配置文件配置日志

Python(37):使用logging的配置文件配置日志 输出日志到控制台和日志文件方法&#xff1a; 创建一个日志配置文件&#xff0c;然后使用fileConfig()函数来读取该文件的内容。 方法1&#xff1a;输出日志到文件&#xff0c;文件是固定的 方法2&#xff1a;输出日志到文件&…

【机器学习】机器学习四大类第01课

一、机器学习四大类 有监督学习 (Supervised Learning) 有监督学习是通过已知的输入-输出对&#xff08;即标记过的训练数据&#xff09;来学习函数关系的过程。在训练阶段&#xff0c;模型会根据这些示例调整参数以尽可能准确地预测新的、未见过的数据点的输出。 实例&#x…

docker安装 unexpected wsl error

docker unexpected wsl error 问题描述&#xff1a; 很诡异的一个问题 大概现象和这个帖子很像 https://developer.aliyun.com/article/1395485 docker版本4.26.1 系统&#xff1a; windows 10 winR 输入winver可以看见自己的版本号 华为matebook 16s 重装的Win10 解决流程…