用Compose一步一步实现一个电音动效

2,131 阅读11分钟

说到电音动效,可能有的人听说过有的人没听说过,生活中我们的确只有在某些特定场合下才会看到电音动效,比如公司年会,DJ派对,或者一些舞台上,都可以用电音动效作为背景来烘托气氛,这个是一个比较专业的领域,如果有兴趣的可以在百度或者某站上搜一下,会看到有很多模版素材,或者一些教程教你制作电音动效,当然,作为一个程序员,也可以用代码去敲一个电音动效出来

目标效果

我从某站上找了一个动效,原版效果就是下面这样的

001.gif

元素还是蛮多的,像这个样子的动画效果肯定不是一口气,几行代码就能搞定的,那么怎么办呢?咱把这个动效分解一下,拆分成若干个子动效,那么问题就可以适当的简化了,于是上面这个动效我们经过拆分,得到如下几个小动效

  • 底部横线以及绕着两端固定点旋转的线
  • 跳动的曲线
  • 高度不停变化的柱状图
  • 从右到左移动的折线
  • 顶部闪烁的动效

现在问题就变成了去实现上面列举的五个小动效,等到这五个动效完成了,我们这个电音动效也就做好了

底部横线以及绕着两端固定点旋转的线

来做第一个,也是看起来最简单的一个,因为这个部分里面有四根线是固定不动的,只需要知道四根线所在的高度,那么就可以很简单的将四根线画出来,四根线对应的y坐标如下

002.png

topLineHeight对应着第一根线的高度,然后底下其余三根线我们看到并不是均匀分布的,而是每一根线与上一根线的间距逐渐拉大,unitHeight是单位高度,其余三根线的y坐标用topLineHeight加上若干个unitHeight计算得出,这样底下四根线就能用drawLine绘制出来了

003.png

这样我们就完成了第一步,效果如下

004.png

接下来就是如何实现两边长度由长到短不断变化的线,仔细观察那些线,会发现它们都有固定的一个起点,左边的线的起点在topLineHeight位置的左边,右边的线在topLineHeight位置的右边,那么这两个起点的坐标就可以轻松的获得,是(0,topLineHeight)(width,topLineHeight),而线的另一头却是一直在沿着边框移动的,如果要计算出这些移动的坐标会相当的麻烦,得换个方式思考,比如我们可以把这线当作是在做圆周运动,圆心分别是(0,topLineHeight)(width,topLineHeight),而半径就是width的长度,大致的轨迹就如下图所示

005.png

要完成这两个90度的圆周运动就简单多了,角度的变化就用两个循环动画来实现,代码如下

006.png

另一头的坐标就用如下两个公式计算出来

007.png

现在使用计算出来的左右侧坐标,我们就可以先画出左右各一根旋转的线,看代码

008.png 009.gif

另外几根线的实现方式也相同,唯一的区别是其他几根线起始角度都不一样,那么这里就创建两个数组用来保存左右两侧的起始角度

010.png

相对应的左右两侧角度的变化值就可以遍历上面两个数组创建出来

011.png

通过角度的变化值,两侧线的end坐标就用如下代码表示

012.png

最后我们再遍历这两个数组,就可以把所有旋转的直线绘制出来了

013.png

到这里我们第一个动效就开发完成了,效果如下

014.gif

跳动的曲线

来做下一个中间那根跳动的曲线,其实那根曲线完全可以当成是一根三阶贝塞尔曲线来做,只是这个贝塞尔曲线的每一个控制点都在不停的上下移动,如果第一时间第一个控制点是由下往上移动,那么第二个控制点必须是由上往下,第三第四个点也是同样以此类推,知道这个以后,获取这四个控制点的y坐标上的值就容易了,分别使用四个循环动画变量来表示这四个y坐标的值

015.png

然后我们利用创建出来的四个y坐标变量,创建一个Path对象,用来绘制贝塞尔曲线

016.png

x坐标取得是两个端点以及width三等份后中间的两个点,最后在Canvas里面调用drawPath函数,我们中间的曲线动效也完成了

017.png 018.gif

高度不停变化的柱状图

第三个动效,看着中间那一堆高低起伏的柱状线,首先一个是肯定的,这个又是要调用一堆drawLine才能完成的动效,那么要使用drawLine的话,最关键的就是要知道start是啥以及end是啥,start很好获取,y坐标就是topLineheight的值,x坐标我们就讲width平分若干份,每一份的x坐标就是单位长度乘上下标值,代码如下

019.png

然后是end坐标,end的x坐标与start相同,而y坐标我们可以看到柱状图呈现的是一个波浪形的样式,那么脑子里面第一个想到的就是正玄余玄函数,也就是通过sin函数计算出y坐标,代码如下

020.png

由于正玄函数的取值范围是-1至1,所以还要通过abs函数取绝对值,然后由于柱状图end位置的y坐标比topLineHeight小,所以正玄出来的值还要被topLineHeight减去才是end位置的坐标,而为了效果明显一些,正玄出来的值我们再让它放大一百倍,最后再结合xListyList,生成一个pList作为存放end位置的Offset的数组,数据部分大概就是这样,我们在Canvas中遍历pList,将这个柱状图画出来看看效果

021.png 022.gif

可以看到已经成功绘制出了一个顶部坐标呈波浪形状的柱状图,现在我们让这个柱状图上下动起来,刚才我们让正玄函数得到的值放大了一百倍,那么如果让这个倍数变成一个可变的值,我们这个柱状图不就可以动起来了吗,下面创建一个循环可变的值

023.png

range作为y坐标放大的倍数

024.png

此时柱状图就可以动起来了

025.gif

但是目前的效果跟视频中的效果比起来还是差距蛮大的,因为视频中我们发现不但柱状图上下有动画,整个波浪的波峰与波谷的数量也会时多时少,那么想让波峰与波谷的数量也能够有变化,我们就要改变count的数量,也就是平分width的份数要有规律的改变,所以再创建个循环动画变量xnum

026.png

将它代替原来的count的固定值后,我们就得到如下效果

027.gif

波峰波谷的个数得到改变后,目前的效果已经比较接近于视频中的效果了,我们再去做下一个动效

从右到左移动的折线

这个在柱状图后面的动效刚开始还真不知道怎么下手,因为就形状来讲它不是啥有规则的几何图形,所以只能选择drawPath或者drawPoints这俩函数中的其中一个,但是前者绘制前需要创建Path对象,而生成一个Path所需要的坐标如果是静态的那么就比较容易,而这里的坐标不但是动态,而且数量也不固定,所以相对来讲drawPoints的优势会大一些,因为它只需要一个Offset数组就可以完成绘制,最后使用PoitModePolygon模式把点连起来就可以了,先创建一个点对象用来保存每个Offset所需要的xy坐标

028.png

Square对象还提供了一个函数update,用来实时更新x坐标,这样整体动画才会看起来像是在向左移动,然后我们还需要两个数组,分别用来保存Square对象以及Square转化后的Offset对象

029.png

由于整个动效是从屏幕右侧开始不断移动至左侧,所以也要从屏幕右侧不断生成新的Square对象,并调用update函数让它逐渐移动至左侧,代码如下

030.png

在副作用LaunchedEffect中监听着sList大小的变化,当有改变的时候就执行副作用里面代码,里面的代码在flow的上游中每过500毫秒就发送个消息给下游,在下游中给sList添加一个新的Square对象,这里值得注意的是,每个Square的初始x坐标是在屏幕右侧朝外width/5距离处,目的是为了屏幕外的点在还没有移动至屏幕内的时候,就可以和屏幕内的点相连,而在副作用的上面,还执行了遍历sList数组批量执行每个Square对象的update函数,执行完之后,再将新的Square转化为Offset保存至offsetList中,最后在Canvas中将这些点都绘制出来

031.png

得到的效果是这样的

032.gif

看起来像是画了一条直线,其实是若干个点在同一个高度造成的,所以接下来我们需要将这些点分层,让它变成一个曲线,视频中不难发现一个规律,那就是曲线会在上方平移一段距离后变化到下方,这样交替的进行,先来个简单的,上下方交替着创建一个点,为此需要创建个布尔值变量记录当前是该在上方创建还是再下方创建

033.png

top值用来记录当前是在上方创建还是下方创建,当为true的时候就在height/2的位置创建一个点,当为false的时候就在topLineHeight位置创建一个点,来看下现在的样子

034.gif

已经达到上下交替创建点的效果了,但是现在交替的时机是创建一个点,我们现在试试创建两个点以后再改变位置,那么就要在增加一个变量用来记录上方或者下方交替前已经创建了几个点

035.png

pointCount就是记录创建点数的计数器,当在上方或者下方创建点的时候,pointCount会自增1,并且判断当前的值是否等于2,如果是的话,就重置pointCount值以及top值,这样就能实现上下交替创建两个点,效果如下

036.gif

已经有个雏形出来了,但是现在上下位置创建的点的高度都是相同的,而视频效果中可以看到上下位置基本都不一样,那么我们也应当在创建点的时候,点的最高点最低点是可以在一个范围值内选取,范围值如下所示

037.png

然后在创建点的地方,把这俩范围值加在y值上,并且在重置状态的位置,重新给上下范围值设置新的值

038.png

此外除了上下的高度,两点之间的距离也是有宽有窄,所以在计算点之间的偏移量上,也应当设置一个范围值,偏移量的范围值如下

039.png

在x值的设置上,也把horizonOffset加上

040.png

在看下现在的效果

041.gif

最后再给折线加点颜色,毕竟视频中的折线是带蓝黑渐变的

042.png 043.gif

顶部闪烁的动效

最后只剩下顶部的闪烁效果了,仔细观察那个闪烁效果不难发现它是带有一点模糊效果的,所以很容易想到要使用blur操作符,但是blur操作符会将画布中所有的元素都模糊化,而我们这里只希望闪烁效果模糊,所以这部分得另开一个画布来绘制

044.png

现在再观察下闪烁效果,我们可以发现,它其实就是四根线交叉在了一起,它们的start端与end端在水平位置上刚好将width四等份,所以这里按照顺序,将start端与end端的x坐标放在两个数组里面

045.png

然后再遍历数组,将四根线绘制出来

046.png 047.gif

线画好了,至于闪烁效果这里就通过不断改变透明值来实现,创建一个透明值的循环动画变量

048.png

将透明值设置在线上

049.png

这样就完成了,看下最终效果

050.gif

总结

整个电音动效已经开发完成了,虽说整体效果跟原版还是有些差距,但是从过程来说,实现里面的这些小动效还是多多少少有些挑战性的,有助于提升动画以及绘图方面的能力,后面还会继续开发有意思的电音动效带给大家,这篇文章就到这里,拜拜~