Compose高级别API动画指南

news2024/11/26 10:40:12

前文讲了Compose中的低级别API动画,与之对应的,还有高级别API动画,同样也符合Material-Design规范。所有高级别动画 API 都是在低级别动画 API 的基础上构建而成,其对应关系如图:
image.gif
接下来就对其高级别API逐个分析:

AnimatedVisibility

即可见性动画,原Google官方文档对此API有实验性标记(可能后面更新就删除了或有其余更改,代码中要用ExperimentalAnimationApi标记),到Compose历经多个版本迭代,这些API依旧坚挺在此,估计后续也不会消失了。

可见性动画,主要是为其内容的出现、消失提供动画效果。先来看下其函数定义:

@ExperimentalAnimationApi
@Composable
fun AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = shrinkOut() + fadeOut(),
    content: @Composable() AnimatedVisibilityScope.() -> Unit
) {
    val transition = updateTransition(visible)
    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
}

在原生View体系动画中,如果想实现隐藏淡入淡出放大缩小等复杂效果,可能需要alpha、scale、transition等动画一起配合实现。但在Compose中,仅通过AnimatedVisibility即可实现,其参数 enter(EnterTransition) 和 exit(ExitTransition)就可以自定义达到淡入淡出的过渡效果。visibile含义就是是否可见,这个没什么好说的。modifier是修饰符,可自定义控件各种属性。content则是指的对应的子控件。从这里可以看出,关键两个参数是EnterTransition和ExitTransition,我们先来了解下它俩。

EnterTransition

Compose对其提供动画函数如下:

fadeIn:从指定的起始alpha到1f淡入。alpha默认为0f,动画规格默认使用spring(spring相关解释在后续的自定义动画中)。

slideIn:从定义的起始偏移量到IntOffset(0,0)的滑动内容。可以通过配置控制滑动方向。正x值表示从右向左滑动,负x值表示从左向右滑动。类似地,正y值和负y值分别对应向上滑动和向下滑动。

expandIn:将显示内容的范围从返回的大小扩展到完整大小。可以控制先显示哪一部分内容。默认情况下,展示内容从IntSize(0,0)至完整大小的动画,从显示内容的右下角(或RTL布局中的左下角)逐渐扩展至显示整个内容。

expandHorizontally:将显示内容的范围从返回的宽度水平扩展到整个宽度。可以控制首先显示哪一部分内容。默认情况下,展示内容从0到全宽的动画,逐渐扩展到显示整个内容。

expandVertically:将显示内容的范围从返回的高度垂直扩展到整个高度。可以控制首先显示哪一部分内容。默认情况下,展示内容从0到全高的动画,首先显示底边,然后显示其余内容。

slideInHorizontally:从定义的起始偏移量到0水平滑动内容(以像素为单位)。可以通过配置控制滑动方向。正值表示从右向左滑动,负值表示从左向右滑动。

slideInVertically:从定义的起始偏移量到0垂直滑动内容(以像素为单位)。可以通过配置控制滑动方向。正值意味着向上滑动,负值意味着向下滑动。

注意,slideIn、slideInVertically、slideInVertically 这种同类型只能同时存在一个。

ExitTransition

同样,Compose对其也提供了一系列动画函数:

fadeOut:从完全不透明到目标alpha淡出效果。默认情况下,内容将淡出为完全透明,动画规格也默认使用spring(spring相关解释在后续的自定义动画中)。

slideOut:从IntOffset(0,0)到定义的目标偏移量的滑动效果。可以通过配置控制方向。x值为正表示从左向右滑动,x值为负表示从右向左滑动。类似地,正y值和负y值分别对应向下滑动和向上滑动。

shrinkOut:展示内容的范围从完整大小缩小到返回的大小的效果。可以控制范围缩小动画的方向。默认情况下,展示内容从完整大小至IntSize(0,0)的动画,并朝内容的右下角(或RTL布局中的左下角)缩小。

shrinkHorizontally:展示内容从整个宽度水平缩小到返回的宽度的效果。可以控制范围缩小动画的方向。默认情况下,剪辑范围从全宽到0设置动画,并朝内容的结尾缩小。

shrinkVertically:展示内容从整个高度垂直缩小到返回的高度消失的效果。可以控制缩小动画的方向。默认情况下,剪辑范围从全高到0设置动画,并朝内容的底部缩小。

slideOutHorizontally:从0到定义的目标偏移量水平滑动内容(以像素为单位)。可以通过配置控制滑动方向。正值表示向右滑动,负值表示向左滑动。

slideOutVertically:从0到定义的目标偏移量垂直滑动内容(以像素为单位)。可以通过配置控制滑动方向。正值表示向下滑动,负值表示向上滑动。

以上内容说起来很抽象,看以下实例:

fun showAnim() {
    var isShow by remember { mutableStateOf(true) }
    Column(
        Modifier.size(300.dp,300.dp),
        Arrangement.Top,
        Alignment.CenterHorizontally
    ) {
        Button(
            onClick = { isShow = !isShow }
        ) {
            Text(text = if (isShow) "Hide" else "Show")
        }

        Spacer(Modifier.height(1.dp))

        AnimatedVisibility(
            visible = isShow,
            enter = slideInVertically() + fadeIn(initialAlpha = 0.1f),
            exit = slideOutVertically() + fadeOut(targetAlpha = 0.1f)
        ) {
            Image(
                painter = painterResource(id = R.drawable.icon_pdx),
                contentDescription = null,
                Modifier.fillMaxSize()
            )
        }
    }
}

其对应效果为:

AnimatedVisibility -1.gif

可以看出,这里有淡入淡出效果的同时,还有上滑下滑效果。通过这种 + 运算符组合多个 EnterTransition 或 ExitTransition 对象(且每个对象都可自定义可选参数和行为)的方式,达到各种效果。其余方法对应效果可以自行验证。

AnimatedContent

内容大小动画,可在内容根据目标状态发生变化时,为内容添加动画效果。其构造函数如下:

@ExperimentalAnimationApi
@Composable
fun <S> AnimatedContent(
    targetState: S,
    modifier: Modifier = Modifier,
    transitionSpec: AnimatedContentScope<S>.() -> ContentTransform = {
        fadeIn(animationSpec = tween(220, delayMillis = 90)) with fadeOut(animationSpec = tween(90))
    },
    contentAlignment: Alignment = Alignment.TopStart,
    content: @Composable() AnimatedVisibilityScope.(targetState: S) -> Unit
) {
    val transition = updateTransition(targetState = targetState, label = "AnimatedContent")
    transition.AnimatedContent(
        modifier,
        transitionSpec,
        contentAlignment,
        content
    )
}

这里可以看出,使用 lambda 参数并将动画反映到内容中(可能描述不太准确),默认情况下,初始内容淡出,然后目标内容淡入(即淡出后淡入)。例如以下示例:

fun showAnim() {
    var data by remember { mutableStateOf(0) }
    Column(
        Modifier
            .fillMaxWidth()
            .fillMaxHeight(),
        Arrangement.Center,
        Alignment.CenterHorizontally
    ) {
        Button(onClick = { data++ }) {
            Text("change")
        }

        AnimatedContent(targetState = data,
        transitionSpec ={ slideInVertically({ fullHeight ->  fullHeight}) with
                slideOutVertically({height -> -height})+ fadeOut()
        }
        ) {
            Text(text = "${data}", fontSize = 36.sp)
        }
    }
}

对应的效果为:

AnimatedContent - 1.gif

以上可见,当 targetState(这里是data) 发生变化时,content 在我们设置的动画中完成切换。

animateContentSize

此方法主要为大小变化添加动画效果。其构造函数如下:

fun Modifier.animateContentSize(
    animationSpec: FiniteAnimationSpec<IntSize> = spring(),
    finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null
): Modifier = composed(
    inspectorInfo = debugInspectorInfo {
        name = "animateContentSize"
        properties["animationSpec"] = animationSpec
        properties["finishedListener"] = finishedListener
    }
) {
    // TODO: Listener could be a fun interface after 1.4
    val scope = rememberCoroutineScope()
    val animModifier = remember(scope) {
        SizeAnimationModifier(animationSpec, scope)
    }
    animModifier.listener = finishedListener
    this.clipToBounds().then(animModifier)
}

不难看出此函数为Modefier的一个扩展函数,示例如下:

fun showAnim() {
    var size by remember { mutableStateOf(Size(100F, 100F)) }
    Column(
        Modifier
            .fillMaxWidth()
            .fillMaxHeight(),
        Arrangement.Center,
        Alignment.CenterHorizontally
    ) {
        Box(
            Modifier
                .animateContentSize()
        ) {
            Image(
                painter = painterResource(id = R.drawable.icon_pdx),
                contentDescription = null,
                Modifier
                    .animateContentSize()
                    .size(size = size.height.dp)
            )
        }
        Spacer(Modifier.height(10.dp))
        Button(
            onClick = {
                size = if (size.height == 100F) {
                    Size(250F, 250F)
                } else {
                    Size(100F, 100F)
                }
            }
        ) {
            Text(if (size.height == 100F) "收缩" else "展开")
        }
    }
}

此部分代码通过变量size监听状态变化实现布局大小的动画效果:

AnimContentSize - 1.gif

Crossfade

此方法主要用于在两个布局之间添加淡入淡出动画,即布局切换动画。通过切换传递给 current 参数的值,使得淡入淡出动画切换内容。其构造函数如下:

@Composable
fun <T> Crossfade(
    targetState: T,
    modifier: Modifier = Modifier,
    animationSpec: FiniteAnimationSpec<Float> = tween(),
    content: @Composable (T) -> Unit
)

targetState和content,在这里,targetState是指定当前的布局状态,content是显示内容,Modifier修饰符在这是修饰Crossfade,animationSpec则是指定动画类型。例如以下示例:

fun showAnim() {
    var page by remember { mutableStateOf(1) }
    Column {
        Button(onClick = { page = if (page == 1) 2 else 1 }) {
            Text("变变变")
        }
        Crossfade(
            targetState = page,
            modifier = Modifier
                .size(600.dp)
                .background(if (page == 1) Color.White else Color.Blue),
            animationSpec = spring()
        ) { screen ->
            when (screen) {
                1 -> Text("Page White", fontSize = 100.sp, color = Color.Blue)
                2 -> Text("Page Blue", fontSize = 100.sp, color = Color.White)
            }
        }
    }
}

对应效果则为:

AnimCrossfade - 1.gif

看到这里,你应该发现了,其实套路都差不多,如果你对低级别API的动画效果还有兴趣,请点这里。下一篇即是Compose的自定义动画。

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

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

相关文章

2024LeetCode分类刷题

一、数组 88. 合并两个有序数组 public void merge(int[] nums1, int m, int[] nums2, int n) {int p1 0, p2 0;int[] sorted new int[m n];while (p1 < m || p2 < n) {int current;if (p1 m) {current nums2[p2];} else if (p2 n) {current nums1[p1];} else i…

单体工程结构

本文主要说明下单体项目的工程结构如何设计&#xff0c;目前业界存在两种主流的应用工程结构&#xff1a;一种是阿里推出的《Java开发手册》中推荐的&#xff0c;另外一种是基于DDD(领域驱动设计)推荐的。下面我们来看下两种工程结构是怎样的。 一、 基于阿里《Java开发手册》…

基于FPGA的UDP实现(包含源工程文件)

1、概括 前文通过FPGA实现了ARP和ICMP协议&#xff0c;ARP协议一般用来获取目的IP地址主机的MAC地址&#xff0c;ICMP通过回显请求和回显应答来判断以太网链路是否通畅&#xff0c;这两个协议都不是用来传输用户数据的。如果用户需要向PC端传输大量数据&#xff0c;那么就必须使…

Swift Combine 通过用户输入更新声明式 UI 从入门到精通十五

Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二Swift Combine 管道 从入门到精通三Swift Combine 发布者publisher的生命周期 从入门到精通四Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五Swift Com…

证明之缺角正方形网格的铺地砖问题

缺角正方形网格的铺地砖问题 “挑战难题&#xff1a;多米诺骨牌与无法覆盖的方格” 这里有个著名的难题。画八横八纵正方形网格&#xff0c;去掉相对的两个角。你能用多米诺骨牌形状的地砖——每一块正好覆盖两个相邻方格&#xff0c;把剩余部分覆盖吗&#xff1f;我在下图中…

bert-vits2本地部署报错疑难问题汇总

环境&#xff1a; bert-vits2.3 win 和wsl 问题描述&#xff1a; bert-vits2本地部署报错疑难问题汇总 解决方案&#xff1a; 问题1: Conda安装requirements里面依赖出现ERROR: No matching distribution found for opencc1.1.6 解决方法 需要在 Python 3.11 上使用 Op…

Springboot加载bootstrap和application原理

Springboot加载bootstrap和application原理 bootstrap.yml能被springboot加载导入依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.6</version><rel…

StringBuilder

StringBuilder代表可变字符串&#xff0c;相当于一个容器&#xff0c;里面的字符串可以改变&#xff0c;用来操作字符串。此类设计用作StringBuffer替代品。 构造方法&#xff1a; StringBuilder() StringBuilder(String str) 操作方法&#xff1a; 1. append()&#xff1…

【Spring】定义过滤器Filter和拦截器Interceptor

# 定义过滤器 package com.holen.filter;import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import java.io.IOException;pub…

2048游戏C++板来啦!

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 大家好呀&#xff0c;我是PingdiGuo_guo&#xff0c;今天我们来学习如何用C编写一个2048小游戏。 文章目录 1.2048的规则 2.步骤实现 2.1: 初始化游戏界面 2.1.1知识点 2.1.2: 创建游戏界面 2.2: 随机…

【开源】基于JAVA+Vue+SpringBoot的班级考勤管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统基础支持模块2.2 班级学生教师支持模块2.3 考勤签到管理2.4 学生请假管理 三、系统设计3.1 功能设计3.1.1 系统基础支持模块3.1.2 班级学生教师档案模块3.1.3 考勤签到管理模块3.1.4 学生请假管理模块 3.2 数据库设…

VS Code主题设置(美化VS Code)(主题+背景+图标+特效+字体)

目录 切换整体主题&#xff08;整体主题&#xff09; 切换文件图标主题 设置VS Code背景图案 字体特效 连击特效 字体设置 主题的具体效果放在了文章末尾&#xff0c;这篇文章后续也会进行更新 ————————————————————————————…

Vulnhub靶机:DC3

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;DC3&#xff08;10.0.2.56&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/dc-32,312…

get和set方法太多太臃肿?快使用 lombok

目录 0. lombok 介绍 1. lombok 使用 1.1 创建一个 maven 项目 1.2 在项目中引用依赖 1.3 在 idea 中添加 lombok 插件 1.4 使用 lombok 注解 1.5 Idea 运行报 Lombok requires enables annotation process 错误解决办法 0. lombok 介绍 当我们写一个类时&#xff0c;为了…

EsayExcel文件导入导出

目录 准备工作 监听器类 导入测试 导出测试 上传Excel 下载Excel 混合导出模板导出 headRowNumber(1)&#xff1a;从第几行开始读 准备工作 导入依赖 <!--easyexcel--> <dependency><groupId>com.alibaba</groupId>x<artifactId>easye…

微服务—ES数据同步

目录 数据同步 问题分析 方案1. 同步调用 方案2. 异步通知 方案3. 监听binlog​编辑 各方案对比 案例——利用MQ实现数据同步 步骤1. 导入hotel-admin项目 步骤2. 声明交换机、队列 步骤3. 发送MQ消息 步骤4. 接收MQ消息 步骤5. 测试同步功能 数据同步 elasticsea…

小白学Halcon100例:如何获取物品中心坐标并展示

文章目录 *读入彩色图片*分解彩色图片为三通道*阈值分割*链接*选择特征*提取目标中心*绘制目标中心--*设置线宽为1*创建十字轮廓*清空窗体*设置绘制模式为绘制边缘*显示目标*显示目标中心*读入彩色图片

算法学习——LeetCode力扣回溯篇4

算法学习——LeetCode力扣回溯篇4 332. 重新安排行程 332. 重新安排行程 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票…

python 基础知识点(蓝桥杯python科目个人复习计划41)

今日复习内容&#xff1a;动态规划&#xff08;基础&#xff09; 动态规划是一种解决多阶段决策过程中最优化问题的数学方法和算法思想。它通常用于解决具有重叠子问题和最优子结构性质的问题&#xff0c;通常将问题划分为相互重叠的子问题&#xff0c;利用子问题的解来求解原…

机器学习3----决策树

这是前期准备 import numpy as np import pandas as pd import matplotlib.pyplot as plt #ID3算法 #每个特征的信息熵 # target : 账号是否真实&#xff0c;共2种情况 # yes 7个 p0.7 # no 3个 p0.3 info_D-(0.7*np.log2(0.7)0.3*np.log2(0.3)) info_D #日志密度…