使用Compose DeskTop实现一个带呼吸灯的秒表

1,076 阅读12分钟

前言

Compose Multiplatform是由Jetbrain团队维护的一个基于Kotlin和Jetpack Compose用于跨多平台的共享UI的声明式框架,目前支持的平台除了Android以外,还有iOS,Web和桌面,如此厉害的技术怎么能不亲自上手尝试一下呢,所以这篇文章要讲的就是使用Compose Desktop开发一个桌面版的秒表应用

准备工作

在开发之前,我们要确定下使用的开发环境,这里我使用的编辑器是IntelliJ IDEA 2022.3.3这个版本,JDK环境用的是11,貌似是最低要求。

image.png

如何创建项目就不说了,现在很多文章都有详细讲解,我们直接开始吧

创建视图

首先我们的秒表肯定是有开始计时与结束计时两个状态,所以我们的界面上需要一个按钮来控制这两个状态,那么第一步就是在main函数的Window组件内绘制出这个按钮

image.png

其中turnOn变量就是我们控制状态的开关,通过Button的点击事件来改变,并且按钮的文案也随着状态的更改显示不同的文字,Clock就是我们绘制秒表的函数,并且接收turnOn这个变量控制秒表的计时。好了以后我们看一下效果

image.png

正如我们预想的一样,一个简单的桌面应用就出来了,接下来就开始绘制我们的秒表

绘制外框

秒表的外框通常来讲就是个圆,而使用Canvas绘制圆有两种选择,一种是使用drawCircle,另一种是使用drawPath,但考虑到drawCircle无法定义边框的大小,所以我们直接使用drawPath函数,绘制Path的话我们需要定义几个变量,分别是中心点坐标,Rect的左上坐标和右下坐标,代码如下

image.png

表盘选取在水平居中位置绘制,其中在中心点的y坐标,以及Rect的y坐标上都加上100的原因主要是为了如果边框的粗细设置的比较大的话,表盘不会被视图遮挡,现在我们就在Canvas中绘制定义好的Path

image.png

一个简单的边框就绘制好了,我们看下效果

111.png

又大又圆,边框绘制完毕,接下去就是表盘的刻度了

绘制刻度

刻度的样式每一种表盘上都不一样,我们这边就简单一些,就在5,10,15这样的刻度上显示文字,其他位置用圆点代替,不然60个数字画一圈怕是太密密麻麻了,那怎么做呢?我们分两步来,第一步先画数字,以下是我们需要用到的变量

image.png

由于绘制数字的方向是在一个圆周上的,所以我们定义一个数组angList存放绘制角度,同时也相对应的定义另一个数组textList存放数字的文案,circleRdius是表盘半径,用来计算圆周坐标用,现在就是要绘制文案了,我们使用DrawScopedrawText函数,有的人会说DrawScope下面哪来的drawText啊?那是因为drawText是在Compose 1.3.0版本推出的,所以如果找不到drawText的话,那就赶紧去更新版本吧,我们看下drawText这个函数都提供了哪些参数

image.png

可以看到必填的参数是前两个,一个是TextMeasurer对象,用来测量文案的,另一个不用多说,设置text,然后topLeft这个属性也是需要的,总不能12个数字都叠在一起吧,知道了要填的参数,我们现在就调用一下drawText

image.png

我们使用rememberTextMeasurer函数创建了一个TextMeasurer对象,并且使用pointXpointY分别计算了每个数字坐上的x,y坐标,两个函数的代码如下

image.png

这里至于为什么要在Offset函数中分别对计算出来的x,y减去20,主要是因为虽然计算出来的坐标是刚好在圆周上,但是当文字绘制出来以后,整体布局会有点偏右下,所以得在结果坐标上再减去20,让文字可以刚好看起来在圆周坐标的中心位置,现在我们运行下代码看下效果怎样

image.png

可以看到数字都画上去了,效果还行,接下来就是圆点刻度,同样定义需要用到的变量

image.png

degreeColor是绘制圆点刻度的颜色,pointAngleList跟上面的anglist一样,是存放圆点角度的数组,虽然说这个数组的大小定义为60,但是在lambda表达式中我们判断了如果计算出来的角度在anglist中已经存在,那么就不赋值用0代替,最终绘制的时候我们判断如果角度为0,那么就不绘制,所以0度的刻度不会被绘制在表盘上,而绘制圆点我们直接使用drawCircle函数,代码如下

image.png

因为同样也是在圆周上,所以计算圆点的坐标也用到了pointx与pointy函数,我们再看下效果

image.png

有内味儿了是不,我们接下来开始画指针

绘制指针

指针其实就是一根line,我们使用drawLine函数就能绘制出来,另外我们在中心点位置再绘制一个圆点,当作是把指针固定在表盘上的一样,代码如下

image.png

其中pointerColor是指针和圆点的颜色,运行一遍代码,我们看到指针已经绘制上去了

image.png

但是指针跟刻度不一样,它得是能绕着圆点动的,怎么动呢?我们看到上面那根静态指针绘制的角度是在angList[0]上,那是不是不停的改变角度,我们的指针就动起来了呢?我们来定一个数组来存在所有需要经过的角度

image.png

totalList就是存放所有角度的数组,至于intervalSize是什么呢,我们知道有的秒表上指针是一格一格走的,间隔比较大,有的间隔比较小,看起来的效果就比较丝滑,intervalSize就是定义指针走动的频率大小的值,并且是能够被360整除的,数组定义好了,我们再给数组下标创建个动画

image.png

这里创建了一个循环动画,因为totalList遍历完一遍以后,代表着一分钟过去了,角度又得重新开始遍历,所以我们给数组的下标值定义了一个循环动画,另外我们还使用LaunchedEffect函数,来监听外部传来的turn值的变化,turn为true的时候,angleIndex的初始值目标值不同,动画开启,turn为false的时候,angleIndex的初始值目标值相同,动画暂停。我们更新下CanvasdrawLine的代码,让drawLine里面获取角度的下标值的变量变成angleIndex

image.png

我们看下效果

aaa1.gif

文字时间

一个秒表的表盘绘制完毕,我们再加点东西,一般性一个秒表底下都会有个文字时间在跳动,差不多由分,秒,毫秒组成,我们这边也加上这些东西,并且在分与秒之间用文字“分”隔开,秒与毫秒之间用“秒”字隔开,那么这五个Text我们要计算出它们topLeft的坐标

image.png

文案的y坐标很容易,就是在表盘底部y坐标上再加点距离就好,至于横坐标,就是找出中间一块区域再五等分,坐标定义完毕,我们先把两个中文绘制出来,x坐标取timeXList下标为1和3的值

image.png

接着我们想一下毫秒位置的数字怎么展示,毫秒位置是在一秒内从0跳到99,然后再从0跳到99,这不又是个循环动画吗,我们仿照指针的动画,将毫秒的动画创建出来

image.png

同样的,因为毫秒的动画也跟随着turn值的变化而改变,所以我们将这个过程也在LaunchEffect中添加上

image.png

现在我们可以在Canvas中将毫秒也绘制出来了

image.png

这边还做了一个处理,当毫秒的值为个位数的时候,我们在数字的边上再加上一个0,让数字跳动的时候看起来效果好一些,毫秒的位置已经绘制完毕,秒的位置也一样,因为它也是从0到60变化的一个循环动画,所以它的代码与毫秒基本差不多

image.png

现在我们再看下效果

aaa2.gif

还剩下分的位置,分就不能用循环动画来实现了,它是一个逐渐递增的过程,当秒的位置为从59变回0的时候,分的位置加一,那么我们就需要一个变量来记录分的值

image.png

minuteValue用来记录分钟的值,然后我们在Canvas里面判断当mainSecondText刚到59的时候,就准备开始给minuteValue加一,为什么是开始准备而不是立马加一呢,因为如果那样做的话,显示的效果是秒的位置一到59秒的时候,分就加一了,这就不符合实际了,我们希望是当59变为0的那会分才加一,所以我们还需要一个状态位,当mainSecondText变为59的时候,状态位打开,直到mainSecondText变成0的时候,状态位才关闭,这个时候分才加一,我们把状态位命名为addMinute

image.png

给分钟设置值的代码如下

image.png

再运行一遍代码看看效果如何

aaa3.gif

完美的衔接起来了,这样一个秒表的功能就基本完成了,我们稍微在点缀一下,如标题所示,加个呼吸灯

呼吸灯效果

在做这个效果之前,这里有个问题,大家是否知道在Compose里面如何给视图设置渐变色?使用drawable吗?Compose里可不兴这些,咱回忆下我们在调用drawpath函数的时候,编辑器是不是会给出这样的提示

image.png

有两个drawPath的函数,这俩函数的区别是在第二个参数上,一个是Color,另一个是Brush,我之前通常都是用Color的,因为Brush是个啥我也不知道,但是当我看到Brush里面的代码以后

image.png

看到第一行注释没,这个其实就是用来做渐变效果的,它比我们传统Android里面设置渐变功能还要丰富,不但渐变的颜色没有限制,方向也没有限制,也就是说你可以在任意两个点之间设置若干种颜色的渐变,现在我们就在我们秒表的边框上设置三种颜色的渐变吧

image.png

首先设置好我们要渐变的颜色值,然后将这个存放颜色值的circleColorList当作参数传入drawPathBrush

image.png

边框的粗细也加大到了30,这样也能清晰的看到渐变效果,现在运行后的效果如下

image.png

效果出来了是不,现在是三个颜色的渐变,那既然刚刚说了Brush的渐变颜色可以是若干个,那么我们在circleColorList中再添加几个颜色试试

image.png

从刚刚的三个变成了六个颜色的数组,再运行一下看看效果会怎么样呢?

image.png

是不是跟刚刚的那个效果图比起来,这个时候的边框渐变色更多了呢,到了这里,咱有个想法,通过之前的循环动画,我们能不能将Brush里面的渐变色值也循环起来呢,比如先设置的是circleColorList下标为0,1,2的颜色,接下去就是显示下标为1,2,3的颜色,以此类推,下标值到了数组末尾,下一个再从头开始,这么做到底会有什么效果呢,我们试一下

image.png

如上述代码所示,我们创建了一个初始值为0,目标值为circleColorList.lastIndex的循环动画,动画时长为两秒,接下去,我们通过判断不同的下标值场景来选取不同的颜色来绘制边框

image.png

由于是三种颜色的渐变,所以场景选择了如果colorIndex为数组最后一个下标,colorIndex为数组倒数第二个下标,以及其他情况,现在我们再来看看边框效果

aaa4.gif

是不是就像表盘周围安置了一个呼吸灯一样,但是这个呼吸灯还不是很完善,因为我们看到的效果,这个呼吸的过程是慢慢从浅色开始,逐渐变深,然后由深变浅是一瞬间的过程,感觉像是这个呼吸被打断了一样,造成这个效果的原因是我们circleColorList数组里面的色值,根据下标的递增是逐渐变深的,但是缺少逐渐变浅的过程,所以我们应该在circleColorList中再增加几个色值,也就是将原来的色值顺序倒转一下添加进去,就像下面这样

image.png

这样就满足了我们呼吸灯由浅变深和由深变浅的两个过程,我们再看看效果

aaa5.gif

总结

Compose DeskTop的秒表功能完成了,这也是我Compose Multiplatform的第一个demo,先选择DeskTop主要是因为几个跨平台里面只有DeskTop与Android的代码算是真正意义上的一套代码跨平台使用,Web主要是多了几个Dom组件,Android里面没法使用,而iOS现在也只是刚刚发布Alpha版,我还在摸索学习中,所以先用DeskTop开个场,后面别的平台的小应用也会相继推出。