同事问我为什么电脑屏幕上会有那么多球在飘

记得以前用的Windows电脑里面,有一个屏保程序就是在屏幕上出现很多飘来飘去的球,当球碰到电脑边缘的时候,会反弹到相反的方向,然后最近就琢磨着能不能使用Compose DeskTop也实现一个这样的效果,那以后我的Mac屏幕上也能出现好多小球,那简直是泰裤辣~

设计思路

我们把整体动效拆分一下总共有五步,每一步都不是很难

  • 第一步:使用循环动画不断改变小球位移的x坐标与y坐标,x坐标的变化范围是0到窗口宽度的最大值,y坐标的变化范围是0到窗口高度的最大值
  • 第二步:判断当x,y坐标到达自己的最大值的边界值的时候,将各自的变化范围的初始值与最终值互相对换一下,达到往相反方向移动的效果
  • 第三步:通过改变tween函数的durationMilliseasing属性,来改变小球的位移速度与位移路线
  • 第四步:将小球的动画需要的属性作为函数的入参,达到可以在上层定制小球动画的效果
  • 第五步:将窗口的宽度与高度更改成屏幕的宽高,背景色改成透明

让球动起来

首先我们来把球的样式做出来,球本身就是个圆形,我们使用Surface组件就可以完成,里面再包一个Box组件,这样做的目的是因为Surface没有办法设置渐变的背景色,我们如果想要让圆形看起来立体一些,就需要让背景色带点渐变,所以渐变的工作就交给里面的Box组件来完成

image.png

然后就可以把这个球放到我们的窗口里面去了,在这之前我们先创建三个常量,分别是窗口的宽高最大值以及小球的大小

image.png

然后把这三个常量分别设置给Window组件以及ball组件,代码与效果就如下图所示

image.png

接下去就是让这个球动起来了,我们通过改变球的位移坐标来实现球体的移动,这里给位移坐标的x,y分别设置一个无限循环动画,动画的初始值是为0,目标值为窗口的宽高,动画时间设置为5秒,然后让这个动画过程线性改变,实现过程如下所示

image.png

我们给Surface组件添加了offset操作符,让它接收mainxmainy的变化值,我们这个球就动起来了

0602aa2.gif

改变位移方向

现在已经让球动起来了,接下来就是要考虑如何让球“碰壁”以后反弹,由于我们的初始位置在窗口左上角,所以我们可以先做碰到下面以后的反弹以及碰到右边以后的反弹,也就是当x坐标到达或者接近x轴位移的最大值,或者y坐标到达或者接近y轴的位移最大值以后,我们将mainxmainy的初始值与目标值对调一下,这样就能往相反方向移动了,注意这里说的是位移最大值,不是窗口的宽高,因为球位移坐标是从球的左上角开始计算的,当碰到窗口边界的时候,其实位移距离是窗口的宽高减去小球的直径大小,所以我们再加上两个常量作为位移的最大值,方便后面计算时使用

image.png

然后如果想要在无限循环动画里面改变初始值与目标值,我们就要使用Animatable来切换,所以这里再创建四个Animatable的变量,分别代表x,y轴的初始值与目标值

image.png

创建好了以后,就直接把mainxmainy的初始值与目标值替换成了新建的四个Animatable变量,这样当我们去切换它们的值以后,mainxmainy的变化范围也发生了改变,而Animatable的切换函数snapTo是一个挂起函数,所以还需要一个协程作用域,我们这里使用rememberCoroutineScope函数来创建,那么小球碰到窗口下边与右边的反弹代码就有了

image.png

这边判断到达边界的条件不是mainx.value.value == offsetx.value的原因是因为通过打印日志发现,mainx或者mainy的变化值不会一直刚好是offsetX.value或者offsetY.value,所以只能把判断当两个值接近的时候当作小球移动到边界的条件,我们运行下看看反弹效果

0602aa3.gif

动图上看不出来,实际效果其实达到边界时候有点细微的抖动,这也跟我们刚刚那个边界值的判断条件有关,不过也不影响功能,我们按照这个方式把碰到左边与上边的代码也加上,一个完整的球体移动动画就做好了

image.png 0602aa4.gif

现在已经能够实现小球碰到窗口四周反弹的效果了,但是实现方式还是比较繁琐的,又是协程又是切换又是看边界值的,我们其实还有更简单的办法,因为不管是x轴的值还是y轴的值,它的变化范围始终在两个值之间,差别就是每次起始位置不同,那么这就是一个反复的过程,而我们这个循环动画其实就可以设置反复模式,使用repeatMode属性,值取RepeatMode.Reverse就可以了,我们试一下

image.png

我们看到现在我们把那四个Animatable都去掉了,mainxmainy的初始值与目标值又回到了固定值,区别就是增加了repeatMode,现在我们在看下实现效果咋样

0602aa5.gif

看起来好像差别不大,但其实碰到边界后的效果比之前要好多了,因为不用去关心那一点误差,而且也可以随意设置动画时间,之前为了让动画的变化值不要变化的太大,所以动画时间我是最小只能设置成5秒,现在的动效看起来就舒服多了

改变速度与路线

动画速度的话我们刚刚其实已经实现了,通过改变动画时间durationMills来实现,但是由于我们的easing设置的是LinearEasing线性变化,所以小球的位移路线永远都是沿着一根直线移动的,我们可以通过改变easing的值,来改变小球的位移路线,比如现在我将easing改成FastOutSlowInEasing

image.png

得到的效果就是这样的

0602aa6.gif

这球一下子就变得好像“有智商”了一样,感觉要“撞了”就马上减速,然后换个方向继续飘

属性作为参数,让小球可定制

想要定制小球动画的话,首先要确定好哪些属性可以拿出来定制,通过上面的开发,我们这个小球动画可以被定制的属性有以下几个

  • ballSize:小球的大小
  • ballColor:小球的颜色
  • xTime:小球位移x轴上的动画时间
  • yTime:小球位移y轴上的动画时间
  • xAnimateEasing:小球x轴上动画的变化速度
  • yAnimateEasing:小球y轴上动画的变化速度

这样子的话我们ball函数的参数列表就如下所示

image.png

然后再将代码中的对应位置用参数来代替

image.png

我们这里把计算最大位移的步骤也移到函数里面了,这样就可以根据不同的小球大小来计算各自的位移距离,我们这个ball函数到这里算是完成了,现在我们就可以想弄几个小球就弄几个小球了,比如我这边就弄了这么几个小球

image.png

下面就是一堆小球的效果

0602aa7.gif

我们再改下小球的样式,将小球弄成背景有点透明的样子,让飘动的小球看起来像是气泡一样,改完以后的小球代码如下

image.png

然后再将调用ball函数的地方,ballColor的入参也改成带点透明值

image.png 0604aa1.gif

如效果图所示,是不是有那么点意思了呢,现在我们进行最后一步。

将窗口透明,宽高增大为全屏

想要将窗口弄成透明的话,可以使用Window组件的transparentundecorated属性,代码如下

image.png

然后把screenWidthscreenHeight大小设置成全屏大小就可以了,我们使用ToolKit来获取屏幕宽高

image.png

还差一步,因为到了这里就算screenWidthscreenHeight设置成全屏宽高了,但实际上所在的窗口并没有真正的全屏,它跟屏幕左边留有一点距离,然后右边延伸至屏幕外边了,所以我们需要让整个窗口居中显示,使用WindowPosition,这个是WindowState里面的一个参数,我们在WindowPosition中设置成居中对齐就可以了

image.png

最终我们得到的效果就是这样的

0604aa2small.gif

总结

整体效果实现起来还是蛮容易的,总共代码加一块也不到一百行,感觉把Window设置成透明以后,DeskTop开发变得好玩多了,大家有兴趣的也可以尝试下,所有元素都可以按照自己喜好来定制,去设计属于自己的屏保程序

猜你喜欢

转载自juejin.im/post/7241567583504941111