Compose 动画艺术探索之灵动岛

news2024/9/23 23:28:55

本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

本篇文章是此专栏的第五篇文章,本篇文章应该是此专栏中最后一篇直接关于动画的文章了,之后文章中可能会提到,但应该不会大幅介绍了。如果想阅读前几篇文章的话可以点击下方链接:

  • Compose 动画艺术探索之瞅下 Compose 的动画
  • Compose 动画艺术探索之可见性动画
  • Compose 动画艺术探索之属性动画
  • Compose 动画艺术探索之动画规格

说起灵动岛,大家肯定都不陌生,因为这段时间这个东西实在是太火了,这是苹果14中算是最大的更新了😂,不拿缺点当缺点,并且还能在缺点上玩出花,这个产品思路确实厉害👍,不得不服!灵动岛看着效果挺炫,其实实现起来并不是特别复杂,今天带大家一起来使用 Compose 实现下属于安卓的“灵动岛”!废话不多说,先来看下本篇文章实现的效果。

在这里插入图片描述

看着还可以吧,哈哈哈,接着往下说!

苹果的灵动岛

在网上找了写灵动岛的视频,大家想看的可以点击链接去看下,肯定比Gif图清晰。

灵动岛视频

灵动岛

嗯,这样看着确实挺好看,如果不是见过真机显示效果我真的就信了😂,不过还是上面说的,思路奇特,大方承认缺点值得肯定!

Compose 简单实现

之前几篇文章大概说了下 Compose 中的动画,思考下这个动画该如何写?我刚看到这个动画的时候也觉得实现起来不容易,但其实转念一想并不难,其实这些动画总结下来就是根据事件不同 Size 的大小也发生了改变,如果在之前原生安卓实现的话会复杂一些,但在 Compose 中就很简单了,还记得之前几篇文章中提到的 animateSizeAsState 么?这是 Compose 中开箱即用的 API,这里其实就可以使用这个来实现,来一起看下代码!

@Composable
fun DynamicScreen() {
    var isCharge by remember { mutableStateOf(true) }
​
    val animateSizeAsState by animateSizeAsState(
        targetValue = Size(if (isCharge) 170f else 100f, 30f)
    )
​
    Column {
        Box(modifier = Modifier
                .width(animateSizeAsState.width.dp)
                .height(animateSizeAsState.height.dp)
                .shadow(elevation = 3.dp, shape = RoundedCornerShape(15.dp))
                .background(color = Color.Black),
        )
​
        Button(
            modifier = Modifier.padding(top = 30.dp, bottom = 5.dp),
            onClick = { isCharge = false }) {
            Text(text = "默认状态")
        }
​
        Button(
            modifier = Modifier.padding(vertical = 5.dp),
            onClick = { isCharge = true }) {
            Text(text = "充电状态")
        }
    }
}

其实核心代码只有一行,就是上面所说的 animateSizeAsState ,其他的代码基本都在画布局,这里使用 Box 来画了下灵动岛的黑色圆角,并且将 box 的背景设置为了黑色,然后画了两个按钮,一个表示充电状态,另一个表示默认状态,点击按钮就可以进行切换,来看下效果!

在这里插入图片描述

大概样式有了,但是不是感觉少了点什么?没错!苹果的动画有回弹效果,但咱们这个没有,那该怎么办呢?还好上一篇文章中咱们讲过动画规格,这里就使用 Spring 就可以满足咱们的需求了,如果想详细了解 Compose 动画规格的话可以移步上一篇文章:Compose 动画艺术探索之动画规格。

来稍微改下代码:

val animateSizeAsState by animateSizeAsState(
    targetValue = Size(if (isCharge) 170f else 100f, 30f),
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioLowBouncy,
        stiffness = Spring.StiffnessMediumLow
    )
)

别的代码都没动,只是修改了下动画规格,再来看下效果!

在这里插入图片描述

嗯,是不是有点意思了!

实现多种切换

上面咱们简单实现了充电的一种状态,但是咱们可以看到苹果里面可不止这一种,上面咱们使用的是 Boolean 值来进行切换的,但如果多种状态的话 Boolean 就有点力不从心了,这个时候就得考虑新的方案了!

private sealed class BoxState(val height: Dp, val width: Dp) {
    // 默认状态
    object NormalState : BoxState(30.dp, 100.dp)
​
    // 充电状态
    object ChargeState : BoxState(30.dp, 170.dp)
​
    // 支付状态
    object PayState : BoxState(100.dp, 100.dp)
​
    // 音乐状态
    object MusicState : BoxState(170.dp, 340.dp)
​
    // 多个状态
    object MoreState : BoxState(30.dp, 100.dp)
}

可以看到上面代码中写了一个密封类,参数就是灵动岛的宽和高,然后根据苹果灵动岛的样式大概可以分为了几种状态:默认状态就是一小条;充电状态高度较默认状态不变,宽度增加;支付状态高度增加,宽度较默认状态不变;音乐状态高度和宽度都较默认状态增加;多个应用状态宽度不变,但会多出一个小黑圆点。

下面还需要修改下状态:

var boxState: BoxState by remember { mutableStateOf(BoxState.NormalState) }

将状态值由 Boolean 改为了刚刚编写的 BoxState ,然后修改下 animateSizeAsState 的使用:

val animateSizeAsState by animateSizeAsState(
    targetValue = Size(boxState.width.value, boxState.height.value), 
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioLowBouncy,
        stiffness = Spring.StiffnessMediumLow
    )
)

接下来再修改下按钮的点击事件:

Button(
    modifier = Modifier.padding(top = 30.dp, bottom = 5.dp),
    onClick = { boxState = BoxState.NormalState }) {
    Text(text = "默认状态")
}
​
Button(
    modifier = Modifier.padding(vertical = 5.dp),
    onClick = { boxState = BoxState.ChargeState }) {
    Text(text = "充电状态")
}

可以看到代码较上面基本没什么改动,只是在点击的时候切换了对应的 BoxState 值。下面再添加几个按钮来对应上面编写的几种状态:

Button(
    modifier = Modifier.padding(vertical = 5.dp),
    onClick = { boxState = BoxState.PayState }) {
    Text(text = "支付状态")
}
​
Button(
    modifier = Modifier.padding(vertical = 5.dp),
    onClick = { boxState = BoxState.MusicState }) {
    Text(text = "音乐状态")
}

嗯,代码很简单,就不过多描述,直接运行看效果吧!

在这里插入图片描述

嗯,效果是不是已经出来了,哈哈哈,是不是很简单,代码实现个简单样式固然不难,但是如果想把系统应用甚至三方应用都适配灵动岛可不是一个简单的事。不过这里咱们值考虑如何实现灵动岛的动画,并不深究系统实现的问题及瓶颈。

多应用状态

上面基本已经实现了灵动岛的大部分动画,但状态中还有一个多应用,就是多个应用在灵动岛上的显示效果还没弄。多应用状态和别的不太一样,别的状态都是灵动岛宽高的变化,但多应用状态会多分出一个小黑圆点,这个需要单独写下。

val animateDpAsState by animateDpAsState(
    targetValue = if (boxState is BoxState.MoreState) 105.dp else 70.dp,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioLowBouncy,
        stiffness = Spring.StiffnessMediumLow
    )
)
​
Box {
    Box(
        modifier = Modifier
            .width(animateSizeAsState.width.dp)
            .height(animateSizeAsState.height.dp)
            .shadow(elevation = 3.dp, shape = RoundedCornerShape(15.dp))
            .background(color = Color.Black),
    )
    Box(
        modifier = Modifier
            .padding(start = animateDpAsState)
            .size(30.dp)
            .shadow(elevation = 3.dp, shape = RoundedCornerShape(15.dp))
            .background(color = Color.Black)
    )
}

可以看到这块又加了一个动画 animateDpAsState 来处理多应用状态小黑圆点的展示,如果当前状态为多应用状态的话即 padding 值增加,这样小黑圆点就会单独显示出来,反之不是多应用状态的话,小黑圆点就会在灵动岛下面进行隐藏,不进行展示。实现效果就是开头的效果了。此处也就不再进行展示。

其他方案实现

上面的动画实现主要使用的是 animateSizeAsState ,这个实现当然是没有问题的,但如果不止需要 Size 的话就不太够用了,比如还需要透明度的变化,亦或者还需要旋转缩放等操作的时候就不够用了,这个时候应该怎么办呢?别担心,官方为我们提供了 updateTransition 来处理这种情况,Transition 可管理一个或多个动画作为其子项,并在多个状态之间同时运行这些动画。

其实 updateTransition 咱们并不陌生,在 Compose 动画艺术探索之可见性动画 这篇文章中也提到过,AnimatedVisibility 源码中就使用到了。

下面来试着将 animateSizeAsState 修改为 updateTransition

val transition = updateTransition(targetState = boxState, label = "transition")
​
val boxHeight by transition.animateDp(label = "height", transitionSpec = boxSizeSpec()) {
    boxState.height
}
val boxWidth by transition.animateDp(label = "width", transitionSpec = boxSizeSpec()) {
    boxState.width
}
​
Box(
    modifier = Modifier
        .width(boxWidth)
        .height(boxHeight)
        .shadow(elevation = 3.dp, shape = RoundedCornerShape(15.dp))
        .background(color = Color.Black),
)

使用方法并不难,可以看到这里使用了 animateDp 方法来处理灵动岛的宽高动画,然后设置了下动画规格,为了方便这里将动画规格抽取了下,其实和上面使用的一致,都是 springtransition 还为我们提供了一些常用的动画方法,来看下有哪些吧!

在这里插入图片描述

上图中的动画方法都可以进行使用,大家可以根据需求来选择使用。

下面来运行看下 updateTransition 实现的效果吧:

在这里插入图片描述

可以看到效果基本一致,如果不需要别的参数直接使用 animateSizeAsState 就足够了,但如果需要别的一些操作的话就可以考虑使用 updateTransition 来实现了。

多个应用切换优化

多应用状态苹果实现的样式中有类似水滴的动效,这块需要使用二阶贝塞尔曲线,其实并不复杂,来看下代码:

Canvas(modifier = Modifier.padding(start = 70.dp)) {
    val path = Path()
    val width = (animateFloatAsState + 30) * density
    val x = animateFloatAsState * density
    val p2x = density * 15f
    val p2y = density * 25f
    val p1x = density * 15f
    val p1y = density * 5f
    val p4x = width - 15f * density
    val p4y = density * 30f
    val p3x = width - 15f * density
    val p3y = 0f
    val c2x = (abs(p4x - p2x)) / 2
    val c2y = density * 20f
    val c1x = (abs(p3x - p1x)) / 2
    val c1y = density * 10f
    path.moveTo(p2x, p2y)
    path.lineTo(p1x, p1y)
    // 用二阶贝塞尔曲线画右边的曲线,参数的第一个点是上面的一个控制点
    path.quadraticBezierTo(c1x, c1y, p3x, p3y)
    path.lineTo(p4x, p4y)
    // 用二阶贝塞尔曲线画左边边的曲线,参数的第一个点是下面的一个控制点
    path.quadraticBezierTo(c2x, c2y, p2x, p2y)
​
    if (animateFloatAsState == 35f) {
        path.reset()
    } else {
        drawPath(
            path = path, color = Color.Black,
            style = Fill
        )
    }
​
    path.addOval(Rect(x + 0f, 0f, x + density * 30f, density * 30f))
    path.close()
    drawPath(
        path = path, color = Color.Black,
        style = Fill
    )
}

嗯,看着其实还挺多,其实并不难,确定好四个个点,然后连接上色就行,然后根据小黑圆点的位置动态绘制连接部分即可,关于贝塞尔曲线在这里就不细说了,大伙应该比我懂。最后来看下效果吧!

在这里插入图片描述

这回是不是就有点像了,哈哈哈!

打完收工

本文带大家一起写了下当下很火的苹果灵动岛,只是最简单的模仿实现,效果肯定不如苹果调教一年的效果,仅供大家参考。

本文所有代码都在 Github 中:https://github.com/zhujiang521/ComposeBookSource

本文至此结束,有用的地方大家可以参考,当然如果能帮助到大家,哪怕是一点也足够了。就这样。

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

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

相关文章

SpringBoot @InitBinder注解实现Bean国际化校验

参考资料 参考: 妥当性チェックのエラーメッセージ出力方法 (需翻墙)springMVC之InitBinder的用法1springMVC之InitBinder的用法2springMVC之InitBinder 和 ValidatorSpring MVCにおけるフォームバリデーションの適用事例【後編】 目录一. 前期准备1.1 自定义校验注…

【spark】第一章——Spark简介及环境配置

文章目录1. Spark 概述1.1 Spark 是什么1.2 Spark and Hadoop1.3 Spark or Hadoop1.4 Spark 核心模块2. Spark 快速上手2.1 创建 Maven 项目2.1.1 增加 Scala 插件2.1.2 增加依赖关系2.1.3 WordCount2.1.4 异常处理3. Spark 运行环境3.1 Local 模式3.1.1 解压缩文件3.1.2 启动 …

MATLAB源码-GRABIT从图像文件中提取数据点。

源码链接: https://download.csdn.net/download/tgs2033/87238015https://download.csdn.net/download/tgs2033/87238015 GRABIT从图像文件中提取数据点。 GRABIT启动用于从图像文件中提取数据的GUI程序。它能够读取BMP、JPG、TIF、GIF和PNG文件(IMREAD…

12月3日:thinkphp模型与数据库相同的部分

定义 定义一个模型类 <?phpnamespace app\index\model; use think\Model;//定义一个User模型类 class User extends Model{//默认主键为自动识别&#xff0c;如果需要指定&#xff0c;可以设置属性//protected $pk uid; //$pk代表主键&#xff0c;primary key的缩写 } …

[附源码]Python计算机毕业设计Django基于JAVA技术的旅游信息交互系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

[附源码]Python计算机毕业设计Django基于Java酒店管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

特征解耦,torch.cumprod(),np.random.seed(),plt.scatter

1.infoGAN 通常&#xff0c;我们学到的特征是混杂在一起的&#xff0c;如上图所示&#xff0c;这些特征在数据空间中以一种复杂的无序的方式进行编码&#xff0c;但是如果这些特征是可分解的&#xff0c;那么这些特征将具有更强的可解释性&#xff0c;我们将更容易的利用这些特…

BI-SQL丨MEGRE

MEGRE MEGRE语句&#xff0c;在SQL的生态圈中&#xff0c;一直都隶属于一个比较重要的位置。 要知道&#xff0c;在实际的项目应用中&#xff0c;我们经常需要从上游数据源&#xff0c;进行原始数据的抽取、清洗、存储、分析等操作&#xff0c;特别是在存储这一环节&#xff…

SpringCloud Ribbon / Feign

文章目录什么是Ribbon&#xff1f;Ribbon的作用&#xff1f;什么是Feign&#xff1f;Feign的作用&#xff1f;什么是Ribbon&#xff1f; Spring Cloud Ribbon 是基于Netflix Ribbon实现的一套客户端负载均衡的工具. Ribbon是Netflix发布的开源项目&#xff0c;主要功能是提供…

介绍HTTP

介绍 HTTP HTTP 协议用于客户端和服务器端之间的通信。请求访问资源的一端被称为客户端&#xff0c; 而提供资源响应的一端被称为服务器端。 HTTP 是一种不保存状态的协议&#xff0c;即无状态&#xff08;stateless&#xff09; 协议&#xff0c;它不对之前发生过的请求和响…

Kotlin高仿微信-第54篇-扫一扫

Kotlin高仿微信-项目实践58篇详细讲解了各个功能点&#xff0c;包括&#xff1a;注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。 Kotlin高仿…

安卓APP源码和报告——音乐播放器

课 程 设 计 报 告 院 系&#xff1a;专 业&#xff1a;题 目&#xff1a;科 目&#xff1a;学 生&#xff1a;指导教师&#xff1a;完成时间&#xff1a;目 录 1. 引言1 1.1 目的1 1.2 背景1 2. 需求分析1 3. 系统设计1 3.1总体设计1 3.2功能设计1 4. 系统开发2 4.1…

秋招经验分享:最终我还是选择了百度

点击进入—>3D视觉工坊学习交流群自我介绍感谢工坊的邀请&#xff0c;来做这次秋招经验的分享。本科和研究生都是自动化专业&#xff0c;研究生期间做移动机器人的定位方向&#xff0c;现在是百度的一名算法工程师&#xff0c;很喜欢现在的工作环境和氛围&#xff0c;强烈推…

【LeetCode每日一题】——72.编辑距离

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【时间频度】九【代码实现】十【提交结果】一【题目类别】 字符串 二【题目难度】 困难 三【题目编号】 72.编辑距离 四【题目描述】 给你两个单词 word…

【Linux】常用的Linux命令(初学者必读)

一、学习Linux的原因 开源&#xff0c;免费系统迭代更新系统性能稳定安全性高多任务&#xff0c;多用户耗资源少内核小应用领域广泛使用及入门容易 二、Linux常用的命令 我使用的Linux环境是在 腾讯云服务器上的Centos 7和 Xshell。 下面我把常用的一些命令分成了几个部分&am…

VPS8505 微功率隔离电源专用芯片2.3-6VIN/24V/1A 功率管 替代金升阳模块

文章目录 前言一、是什么&#xff1f;二、特点三、应用领域四、简化应用五、引脚及功能六、参数测试电路 总结前言 隔离电源市场&#xff0c;一直被塑封模块产品占领&#xff0c;之前国内无专业 做隔离芯片的厂家&#xff0c;市场以模块厂进口芯片方案为主&#xff1b;…

深入 Java 线程池:从设计思想到源码解读

为什么需要线程池 我们知道创建线程的常用方式就是 new Thread() &#xff0c;而每一次 new Thread() 都会重新创建一个线程&#xff0c;而线程的创建和销毁都需要耗时的&#xff0c;不仅会消耗系统资源&#xff0c;还会降低系统的稳定性。在 jdk1.5 的 JUC 包中有一个 Execut…

从实用角度浅析前端全链路质量监控中台技术方案

大厂技术 高级前端 Node进阶点击上方 程序员成长指北&#xff0c;关注公众号回复1&#xff0c;加入高级Node交流群感谢作者陈煮酒的投稿。前言无论是纯前端业务还是服务端业务&#xff0c;线上质量的保障都是我们的底线要求&#xff0c;也是我们日常需要花费很多精力关注的环…

【大数据入门核心技术-Zookeeper】(五)ZooKeeper集群搭建

目录 一、准备工作 1、集群分布 2、创建数据目录 3、下载安装包 二、解压安装 1、解压 2、修改配置文件zoo.cfg 3、添加myid配置 4、分发zk文件夹和分别新建myid 5、配置环境变量 6、三台机器分别启动zookeeper服务 一、准备工作 1、集群分布 服务器IP主机名myid的…

有损压缩与无损压缩

有损压缩与无损压缩数据压缩有损压缩无损压缩有损压缩与无损压缩的区别Which One to Use?Final Words有损压缩、无损压缩&#xff08;图片、音频、视频&#xff09;图片文件格式音频文件格式视频文件格式数据压缩 数据压缩&#xff08;Data Compression&#xff09;是减小任何…