Compose 动画 (三) : AnimatedVisibility 从入门到深入
1. AnimatedVisibility 是什么
AnimatedVisibility
可以实现Compose组件
的显示和隐藏,并且可以指定显示/隐藏时候的动画效果。(EnterTransition/ExitTransition
)
和 animateXxxAsState、animateContentSize
、Crossfade
、AnimatedContent
这几个API一起,都是Compose
的高级别动画API,是比较易用的。
2. AnimatedVisibility 的基础使用
@Composable
fun AnimatedVisibilityPage() {Column(horizontalAlignment = Alignment.CenterHorizontally) {var visible by remember {mutableStateOf(true)}AnimatedVisibility(visible = visible) {Image(painter = painterResource(id = R.mipmap.photot1),modifier = Modifier.width(300.dp),contentDescription = null)}Spacer(modifier = Modifier.height(10.dp))Button(onClick = { visible = !visible }) {Text(text = "显示/隐藏")}}
}
看上去是不是很简单,只需要在Image
外层包上AnimatedVisibility
就可以了,显示效果如下
我们点进AnimatedVisibility
的源码,可以看到如下代码
@Composable
fun ColumnScope.AnimatedVisibility(visible: Boolean,modifier: Modifier = Modifier,enter: EnterTransition = fadeIn() + expandVertically(),exit: ExitTransition = fadeOut() + shrinkVertically(),label: String = "AnimatedVisibility",content: @Composable AnimatedVisibilityScope.() -> Unit
){val transition = updateTransition(visible, label)AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
}
可以发现其内部调用了updateTransition
,该函数内部会返回Transition
对象。
@Composable
fun <T> updateTransition(targetState: T,label: String? = null
): Transition<T> {//...省略...
}
Transition
可管理一个或多个动画作为其子项,并在多个状态之间同时运行这些动画。
这个我们后续文章会讲到,现在先知道有这样一个概念就好。
3. 入场和出场效果
enter
和exit
,用来配置入场/出场
时候的动画效果。
默认的入场效果是 fadeIn() + expandVertically()
默认的出场效果是 fadeOut() + shrinkVertically()
3.1 EnterTransition
和ExitTransition
支持的动画
enter
的参数类型是EnterTransition
,支持这些动画
fade: fadeIn
scale: scaleIn
slide: slideIn, slideInHorizontally, slideInVertically
expand: expandIn, expandHorizontally, expandVertically
exit
的参数类型是EnterTransition
fade: fadeOut
scale: scaleOut
slide: slideOut, slideOutHorizontally, slideOutVertically
shrink: shrinkOut, shrinkHorizontally, shrinkVertically
可以看到EnterTransition
和ExitTransition
支持的动画只有expand
和shrink
命名上有区别,
其他都是对应的 fadeIn
和 fadeOut
,scaleIn
和 scaleOut
,slideIn
和 slideOut
。
expand
和shrink
命名上做区分,是因为expand
就是展开
的意思,而shrink
是收缩
的意思,它们其实就是相对应的。
3.1.1 fadeIn / fadeOut
fadeIn / fadeOut
是淡出淡出的效果
我们依旧使用上面的那段代码
@Composable
fun AnimatedVisibilityPage() {Column(horizontalAlignment = Alignment.CenterHorizontally) {var visible by remember {mutableStateOf(true)}AnimatedVisibility(visible = visible) {Image(painter = painterResource(id = R.mipmap.photot1),modifier = Modifier.width(300.dp),contentDescription = null)}Spacer(modifier = Modifier.height(10.dp))Button(onClick = { visible = !visible }) {Text(text = "显示/隐藏")}}
}
先把图片部分抽取为一个Composable
函数
@Composable
private fun MyImage() {Image(painter = painterResource(id = R.mipmap.photot1),modifier = Modifier.width(300.dp),contentDescription = null)
}
然后修改AnimatedVisibility
,配置enter
和exit
AnimatedVisibility(visible = visible,enter = fadeIn(),exit = fadeOut()
) {MyImage()
}
效果如下所示
3.1.2 scaleIn / scaleOut
scaleIn / scaleOut
是缩放的效果
AnimatedVisibility(visible = visible,enter = scaleIn(),exit = scaleOut()
) {MyImage()
}
效果如下所示
3.1.3 slideIn / SlideOut
slideIn / SlideOut
是滑动的效果,这里进入的初始位置和退出的目标位置都设置为了(300,-150)
,所以会从右上角进入/退出
AnimatedVisibility(visible = visible,enter = slideIn(initialOffset = {IntOffset(300, -150) }),exit = slideOut(targetOffset = {IntOffset(300,-150) })
) {MyImage()
}
效果如下所示
3.1.3.1 slideInVertically / slideOutVertically
slideInVertically / slideOutVertically
是垂直方向滑动进入/退出
AnimatedVisibility(visible = visible,enter = slideInVertically(),exit = slideOutVertically()
) {MyImage()
}
效果如下所示
3.1.3.2 slideInHorizontally / slideOutHorizontally
slideInHorizontally / slideOutHorizontally
是从横向方向滑动进入/退出
AnimatedVisibility(visible = visible,enter = slideInHorizontally(),exit = slideOutHorizontally()
) {MyImage()
}
效果如下所示
3.1.4 expandIn / shrinkOut
expandIn / shrinkOut
是展开/收缩
的效果
AnimatedVisibility(visible = visible,enter = expandIn(),exit = shrinkOut()
) {MyImage()
}
效果如下所示
3.1.4.1 expandVertically / shrinkVertically
expandVertically / shrinkVertically
是从垂直方向展开/收缩
AnimatedVisibility(visible = visible,enter = expandVertically(),exit = shrinkVertically()
) {MyImage()
}
效果如下所示
3.1.4.2 expandHorizontally / shrinkHorizontally
expandHorizontally / shrinkHorizontally
是从横向方向展开/收缩
AnimatedVisibility(visible = visible,enter = expandHorizontally(),exit = shrinkHorizontally()
) {MyImage()
}
效果如下所示
3.1.5 EnterTransition
和ExitTransition
的源码
再来看下源码,入场动画EnterTransition
和出场动画ExitTransition
内部都有TransitionData
变量
sealed class EnterTransition {internal abstract val data: TransitionData//...
}
sealed class ExitTransition {internal abstract val data: TransitionData//...
}
TransitionData
即是可配置的动画参数,分别对应fade
、slide
、expand/shrink
、scale
internal data class TransitionData(val fade: Fade? = null,val slide: Slide? = null,val changeSize: ChangeSize? = null,val scale: Scale? = null
)
我们可以发现 EnterTransition 和 ExitTransition 是 sealed class,密封类
其子类可以出现在定义 sealed class 的不同文件中,但不允许出现在与不同的 module 中,且需要保证 package 一致
这样既可以避免 sealed class 文件过于庞大,又可以确保第三方库无法扩展你定义的 sealed class,达到限制类的扩展目的
3.2 +
号的作用
AnimatedVisibility
源码的部分,入场(enter
)和出场(exit
)的配置,使用了+
,这个加号是用来做什么的呢 ?
首先,+
号是Kotlin
的一个特性 : 重载运算符
我们点击这个+
号,就可以跳转到它的源码
@Stable
operator fun plus(enter: EnterTransition): EnterTransition {return EnterTransitionImpl(TransitionData(fade = data.fade ?: enter.data.fade,slide = data.slide ?: enter.data.slide,changeSize = data.changeSize ?: enter.data.changeSize,scale = data.scale ?: enter.data.scale))}
如果data
不为空,就用data
的值,否则用enter/exit
的。
这里的data
就是EnterTransition
和ExitTransition
中的那个变量internal abstract val data: TransitionData
(见 3.1 EnterTransition
和ExitTransition
支持的动画部分)
所以fadeIn() + expandVertically()
,fadeIn()
会赋值给TransitionData
的fade
,expandVertically
会赋值给changeSize
即 : 合并各个动画的效果
3.2.1 两个相同的动画会有什么效果
如果是两个相同的动画,比如fadeIn(initialAlpha = 0.3f) + fadeIn(initialAlpha = 0.5f)
根据源码中的这个规则 如果data不为空,就用data的值,否则用enter/exit的。
可知 :
两个fade
,会重叠了,导致后面那部分不会生效,等同于fadeIn(initialAlpha = 0.3f)
,+
号左边优先级高
3.3 多种动画效果结合
我们来尝试下多种动画效果,使用+
号合并之后的效果
val density = LocalDensity.current
AnimatedVisibility(visible = visible,enter = slideInVertically {//从顶部-200dp的位置开始滑入with(density) { -200.dp.roundToPx() }} + expandHorizontally(//展开位置expandFrom = Alignment.End) + fadeIn(//从初始透明度0.3f开始淡入initialAlpha = 0.3f),exit = slideOutHorizontally() + shrinkHorizontally() + fadeOut()
) {MyImage()
}
效果如下所示
4. 不同的作用域
4.1 AnimatedVisibility的作用域
AnimatedVisibility
有好几种作用域,区别在于在这几种布局中,默认的入场动画
和出场动画
是不同的
Column
默认的出入场动画具有垂直展开
,而Row
的出入场动画具有横向展开
,Transition
和无前缀
的出入场动画是展开/收缩
。
Column
fun ColumnScope.AnimatedVisibility(visible: Boolean,modifier: Modifier = Modifier,enter: EnterTransition = fadeIn() + expandVertically(),exit: ExitTransition = fadeOut() + shrinkVertically(),label: String = "AnimatedVisibility",content: @Composable AnimatedVisibilityScope.() -> Unit
)
Row
fun RowScope.AnimatedVisibility(visible: Boolean,modifier: Modifier = Modifier,enter: EnterTransition = fadeIn() + expandHorizontally(),exit: ExitTransition = fadeOut() + shrinkHorizontally(),label: String = "AnimatedVisibility",content: @Composable() AnimatedVisibilityScope.() -> Unit
)
Transition
@ExperimentalAnimationApi //实验的动画API
@Composable
fun <T> Transition<T>.AnimatedVisibility(visible: (T) -> Boolean,modifier: Modifier = Modifier,enter: EnterTransition = fadeIn() + expandIn(),exit: ExitTransition = shrinkOut() + fadeOut(),content: @Composable() AnimatedVisibilityScope.() -> Unit
) = AnimatedEnterExitImpl(this, visible, modifier, enter, exit, content)
无前缀
fun AnimatedVisibility(visible: Boolean,modifier: Modifier = Modifier,enter: EnterTransition = fadeIn() + expandIn(),exit: ExitTransition = shrinkOut() + fadeOut(),label: String = "AnimatedVisibility",content: @Composable() AnimatedVisibilityScope.() -> Unit
)
4.2 使用不了AnimatedVisibility
如果我们在Column
里面有个Box
,Box
里面又有AnimatedVisibility
,会发现AnimatedVisibility
会报错
我们把鼠标移到这个报红的地方,可以看到如下的提示
这边提示
'fun ColumnScope.AnimatedVisibility(visible: Boolean, modifier: Modifier = ..., enter: EnterTransition = ..., exit: ExitTransition = ..., label: String = ..., content: AnimatedVisibilityScope.() -> Unit): Unit' can't be called in this context by implicit receiver. Use the explicit one if necessary
注意最后一句
也就是说,不能使用隐式调用,而必须得用显式的。因为AnimatedVisibility
有好几种作用域,ColumnScope
和全局的作用域,IDE
不知道该引用哪个了。
我们可以显示添加this@Column.
,这样,就会引用Column
的那个AnimatedVisibility
了
当然也可以包装一层Compose
函数,使用全局作用域的AnimatedVisibility
这两种都是可行的
5. 其他
Compose 动画系列,后续持续更新,可以先关注
Compose 动画 (一) : animateXxxAsState 实现放大/缩小/渐变等效果
Compose 动画 (二) : 为什么animateDpAsState要用val ? MutableState和State有什么区别 ?
相关文章:

Compose 动画 (三) : AnimatedVisibility 从入门到深入
1. AnimatedVisibility 是什么 AnimatedVisibility可以实现Compose组件的显示和隐藏,并且可以指定显示/隐藏时候的动画效果。(EnterTransition/ExitTransition) 和 animateXxxAsState、animateContentSize、Crossfade、AnimatedContent 这几个API一起,都…...

网络基础(二)
目录 应用层 再谈 "协议" 协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢? 为什么要转换呢? 如果我们将struct message里面…...

Java线程知识点总结
文章目录Java 线程基础线程简介什么是进程什么是线程进程和线程的区别创建线程ThreadRunnableCallable、Future、FutureTaskCallableFutureFutureTaskCallable Future FutureTask 示例线程基本用法线程休眠线程礼让终止线程守护线程线程通信wait/notify/notifyAlljoin管道线程…...

数据结构——第三章 栈与队列(4)
队列的应用1.基于队列的医院挂号模拟系统2.队列的运用1.基于队列的医院挂号模拟系统 代码实现分享 2.队列的运用 问题描述:某运动会设立N个比赛项目,每个运动成员可以参加1~3个项目。试问如何安排比赛日程,既可以使同一运动员参加的项目不…...

华为机试HJ73-计算日期到天数转换
HJ73 计算日期到天数转换 题目描述: 描述 根据输入的日期,计算是这一年的第几天。 保证年份为4位数且日期合法。 进阶:时间复杂度:O(n) ,空间复杂度:O(1) 输入描述: 输入一行,每行…...

【阅读笔记】你不知道的JavaScript--this与对象2
目录this默认绑定隐式绑定隐式丢失显示绑定API 调用上下文new 绑定this 绑定优先级其余绑定例外对象字面量与对象属性描述符迭代器遍历this 默认绑定 默认绑定适配 独立函数调用 默认绑定 this 指向全局对象; 故直接调用函数,该函数内部的 this 即指向全…...

单板TVS接地不当造成辐射骚扰超标问题分析-EMC
【摘要】 某产品EMC辐射骚扰测试超标,通过近远场扫描配合定位分析,逐步找出骚扰源、传播路径,最终通过修改 PCB 走线切断传播路径解决此问题。 1 故障现象 某产品在进行 EMC 研发摸底测试时发现,整机辐射骚扰垂直方向测试超标&a…...

用Python Flask为女朋友做一个简单的网站(附可运行的源码)
🌟所属专栏:献给榕榕🐔作者简介:rchjr——五带信管菜只因一枚😮前言:该专栏系为女友准备的,里面会不定时发一些讨好她的技术作品,感兴趣的小伙伴可以关注一下~👉文章简介…...

vue3+rust个人博客建站日记5-所有界面
没有数据的前端,是没有灵魂的。明明标题是vue3 rust ,但日记撰写至今,似乎只有第一篇提及了Rust,这可不行。是时候一股作气,完成大部分页面绘制工作了! 最后再说一次,时间要加速了。 ——普奇神…...

青少年软件编程C++一级真题(202212)
1、输入一个整数x,输出这个整数加1后的值,即x1的值。 时间限制:1000 内存限制:65536 输入 一个整数x(0 ≤ x ≤ 1000)。 输出 按题目要求输出一个整数。 样例输入 9样例输出 10 #include<iost…...

【Spring】AOP底层原理(动态代理)-》 AOP概念及术语 -》 AOP实现
个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ AOP - 面向切面编程一、简述AOP二、AOP底层原理…...

Java8 新特性 之 lambda 表达 和 函数式接口
—— lambda 表达式 概念 lambda 表达式是一个匿名函数,可以把 lambda 表达式理解为是一段可以传递的代码。更简洁、更灵活,使 Java 的语言表达能力得到了提升lambda 表达式是作为接口的实现类的对象(万事万物皆对象) 使用语法…...

Netty服务端和客户端开发实例
一、Netty服务端开发在开始使用 Netty 开发 TimeServer 之前,先回顾一下使用 NIO 进行服务端开发的步骤。(1)创建ServerSocketChannel,配置它为非阻塞模式;(2)绑定监听,配置TCP 参数,例如 backlog 大小;(3)创建一个独立的I/O线程&…...

linux基本指令和权限
目录 一.shell命令以及运行原理 二.Linux常用指令 1. ls 指令 2. pwd命令 3.cd指令 4. touch指令 5.mkdir指令(重要) 6.rmdir指令 && rm 指令(重要) 7.man指令(重要) 8.cp指令(重要&…...

滚蛋吧,正则表达式!
大家好,我是良许。 不知道大家有没有被正则表达式支配过的恐惧?看着一行火星文一样的表达式,虽然每一个字符都认识,但放在一起直接就让人蒙圈了~ 你是不是也有这样的操作,比如你需要使用「电子邮箱正则表达式」&…...

序列号和反序列化--java--Serializable接口--json序列化普通使用
序列化和反序列化序列化和反序列化作用为什么需要用途Serializable使用serialVersionUID不设置的后果什么时候修改Externalizable序列化的顺序json序列化序列化和反序列化 序列化:把对象转换为字节序列的过程称为对象的序列化。 反序列化:把字节序列恢复为对象的过…...

Java异步任务编排
多线程创建的五种方式: 继承Thread类实现runnable接口。实现Callable接口 FutureTask(可以拿到返回结果,阻塞式等待。)线程池创建。 ExcutorService service Excutors.newFixedThreadPool(10); service.excute(new Runnable01());另外一种创建线程池…...

Hive与HBase的区别及应用场景
当数据量达到一定量级的时候,存储和统计计算查询都会遇到问题,今天了解一下Hive和Hbase的区别和应用场景。 一、定义 Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供简单的sql查询功能&am…...

C++之单例模式
目录 1. 请设计一个类,只能在堆上创建对象 2. 请设计一个类,只能在栈上创建对象 3.请设计一个类,不能被拷贝 C98 C11 4. 请设计一个类,不能被继承 C98 C11 5. 请设计一个类,只能创建一个对象(单例模式) 设计…...

Redis十大类型——Set与Zset常见操作
Redis十大类型——Set与Zset常见操作Set命令操作简列基本操作展示删除移动剪切集合运算Zset基本操作简列添加展示反转按分数取值获取分数值删除分数操作下标操作如果我们对Java有所了解,相信大家很容易就明白Set,在Redis中也一样,Set的value值…...

车载雷达实战之Firmware内存优化
内存(Memory)是计算机中最重要的部件之一,计算机运时的程序以及数据都依赖它进行存储。内存主要分为随机存储器(RAM),只读存储器(ROM)以及高速缓存(Cache)。仅仅雷达的原…...

【剑指Offer】JZ14--剪绳子
剪绳子详解1.问题描述2.解题思路3.具体实现1.问题描述 2.解题思路 首先想到的思路:因为是求乘积的最大值,所以如果截取剩下的是1,那还是它本身就没有意义。从此出发,考虑绳子长度是2、3、4、5…通过穷举法来找规律。 值–》拆分–…...

raspberry pi播放音视频
文章目录目的QMediaPlayerGStreamerwhat is GStreamer体系框架优势omxplayerwhat is omxplayercommand Linekey bindings运行过程中错误ALSA目的 实现在树莓派下外接扬声器, 播放某段音频, 进行回音测试。 QMediaPlayer 首先我的安装是5.11版本。 优先…...

【电子学会】2022年12月图形化二级 -- 老鹰捉小鸡
老鹰捉小鸡 小鸡正在农场上玩耍,突然从远处飞来一只老鹰,小鸡要快速回到鸡舍中,躲避老鹰的抓捕。 1. 准备工作 (1)删除默认白色背景,添加背景Farm; (2)删除默认角色小…...

C++的双端队列
双端队列介绍1.双端队列知识需知2.大试牛刀1.双端队列知识需知 由于队列是一种先进先出(FIFO)的数据结构,因此无法直接从队列的底部删除元素。如果希望从队列的底部删除元素,可以考虑使用双端队列(deque)。…...

【独家】华为OD机试 - 拼接 URL(C 语言解题)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明本期…...

为什么使用Junit单元测试?Junit的详解
Hi I’m Shendi 为什么使用Junit单元测试?Junit的详解 Junit简介 Junit是一个Java语言的单元测试框架。 单元测试是一个对单一实体(类或方法)的测试 JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression test…...

怎么学好嵌入式Linux系统和驱动
嵌入式专业是一门实践性非常强的学科,只有多动手,多实践,多编程,多调试,多看书,多思考才能真正掌握好嵌入式开发技术。 现在很多同学也意识到了学校培养模式和社会需求脱节问题,有一部分同学也先…...

Spring Aware总结
概述 Spring中Aware到底是什么意思? 我们在看Spring源码的时候,经常可以看到xxxAwarexxx的身影,通常我会很疑惑,Aware到底是什么意思呢? 比如图片中这些包含Aware关键字的类或者接口。 我对下面3个类或接口进行了解…...

【RocketMQ】源码详解:Broker端消息刷盘流程
消息刷盘 同步入口:org.apache.rocketmq.store.CommitLog.GroupCommitService 异步入口:org.apache.rocketmq.store.CommitLog.FlushRealTimeService 刷盘有同步和异步两种,在实例化Commitlog的时候,会根据配置创建不同的服务 p…...