主机参考:VPS测评参考推荐/专注分享VPS服务器优惠信息!若您是商家可以在本站进行投稿,查看详情!此外我们还提供软文收录、PayPal代付、广告赞助等服务,查看详情! |
我们发布的部分优惠活动文章可能存在时效性,购买时建议在本站搜索商家名称可查看相关文章充分了解该商家!若非中文页面可使用Edge浏览器同步翻译!PayPal代付/收录合作 |
今天微信小程序开发教程专栏教你实现网易云音乐宇宙尘特效,手把手教你。
前言前段时间女朋友用网易云音乐,看到一个宇宙尘埃特效,说很好看,想让我给她VIP使用。
笑话,你一个程序员怎么就自己体会不到呢!什么VIP!!
什么女朋友?有程序员吗?我只关心特效的实现!
在2002年,大多数Android开发者应该都很圆滑。如果你对自定义视图还不够熟练,那就没有意义了。自定义视图可以说是Android开发中必须掌握的一个点,无论是初级、中级还是高级。
不然UI设计不小心太酷了,你就不想和他斗了?你不想成为下图中的那个人吗?
因此,自定义视图的重要性就不用我多说了。本文针对的是有自定义视图基础知识,但苦于没有好的项目模仿,或者看到酷炫效果后不知如何下手的人。恭喜你,我带你一步一步分析效果,然后代码实现。
我知道没有照片我骗不了任何人。先放图吧。我们来看看最后的效果。
Ps:为了加载的更快,gif被压缩压缩,让每个人都有一个清晰的大脑。
Ps2:小伙伴们如果有好的gif压缩网站可以推荐一波。
嗯,虽然画质堪比AV画质,但还是能看出来效果很好。所以今天我就带朋友们从头到尾体会一下这种效果。
特效分析先看动图。我们可以把它拆分成两部分,一部分是内部不断旋转的圆形画面,一部分是外部不断扩散的粒子动态效果。
我们将从易到难做它。毕竟柿子要软的。
此外,由于本文侧重于自定义视图,所以我们不使用ViewGroup的方式来实现图片和粒子动力学的结合。但是在单独的布局中。这样做的好处是可以只关注粒子动力学的实现,不考虑测量、布局等。
至于自定义ViewGroup,我会在下一篇文章中带领大家实现一个非常非常非常酷的效果。
我们先观察一下画面。首先,这是一张圆图。其次,它一直在转。
好了,先别骂我,让我说完。
如果图片是圆形的,我们就用Glide来实现。其实自定义视图实现也是可以的,但是我们的重点是粒子特效。
首先定义一个ImageView。
lt?xml版本= 1.0 编码= ut F-8 ;? gt ltrelative layout xmlns:Android = ;http://schemas.android.com/apk/res/android&; xmlns:app = ;http://schemas.android.com/apk/res-auto&; xmlns:tools = ;http://schemas.android.com/tools&; Android:id = ;@+id/root layout ;Android:layout _ width = ;匹配父母 Android:layout _ height = ;匹配父母 Android:fitsSystemWindows = ;true gt ltImageView Android:id = ;@+id/music _ avatar ;Android:layout _ centerInParent = ;true Android:layout _ width = ;178dp Android:layout _ height = ;178dp / gt; lt/relative layout gt;复制代码现在让我们去活动,用Glide加载一个圆形图片。
class demo activity:app compat activity(){ private late init var demo binding:ActivityDemoBinding override fun onCreate(savedInstanceState:Bundle?){ super . oncreate(savedInstanceState)demo binding = activitydemobinding . inflate(layoutInflater)set content view(demo binding . root)life cycle scope . launch(Dispatchers。main){ loadImage()} } private suspend fun loadImage(){ with context(Dispatchers。IO){ glide . with(this @ demo activity)。load(R.drawable.ic_music)。circleCrop()。into(object:ImageViewTarget lt;Drawable gt(demo binding . music avatar){ override fun set resource(resource:Drawable?){ demo binding . musicavatar . setimagedrawable(resource)} } }复制代码,这样我们就可以用Glide加载一个圆形图片了。
旋转图片。现在你有了图片,接下来你应该旋转它。
那我们开始转吧。
旋转是如何实现的?我想我不用多说了。很多朋友都知道是动漫。
没错,就是动漫。这里我们用属性动画来实现。
定义一个属性动画,并为图片设置一个单击事件,使其旋转。
late init var rotate animator:objectanimator override fun onCreate(savedInstanceState:Bundle?) { ...setContentView(demo binding . root)rotate animator = object animator . off loat(demo binding . music avatar,View。ROTATION,0f,360 f)rotate animator . duration = 6000 rotate animator . repeat count = -1 rotate animator . interpolator = linear interpolator()生命周期scope . launch(dispatchers . main){ loadimage()//添加一个click事件,启动动画demo binding . musicavatar . setonclicklistener { rotate animator . start()} }复制代码。这些都是小儿科。我相信面对电视机前的观众朋友,啊不,口误。
相信朋友们都很熟悉,那就开始今天的重头戏,这个粒子动画吧。
粒子动画其实很久以前看粒子动画的时候,我也很好奇这些很酷的粒子动画是怎么实现的。当时我一点概念都没有。
尤其是看到一些图片,啪嗒一声变成一堆颗粒,掉下来,然后从颗粒啪嗒变成图片,感觉异常牛x。
其实一点都不惊艳。
首先,我们要知道位图是什么。什么是位图?
在数学中,有几个概念,如点、线、面。一个点很好理解,就是一个点。线是由一堆点组成的,而面类似于一堆线。本质上,一个曲面是由无数个点组成的。
但是这跟位图和今天的粒子动画有什么关系呢?
位图,我们可以简单理解为图片。这张图片是一架飞机吗?而平面是由一堆点组成的,这里叫做像素。所以位图是由一堆像素组成的。有趣的是,这些像素是有颜色的。当这些像素足够小,你离得足够远,你看起来就像一幅完整的画面。
现实中,这样的例子还有很多。当一些活动举行时,一个人站在不同颜色的广场上。如果空中有无人机,可以看成一幅画。像这样
所以当你把一张图片分解成一堆粒子的时候,你实际上可以得到位图中的所有像素,并改变它们的位置。如果要用一堆粒子拼凑出一幅图,只需要知道这些粒子的顺序,排列整齐自然就是一幅图。
太远了。其实和今天的效果无关,只是为了让你更好的理解粒子动画的本质。
粒子动画分析我们先来观察一下这个特效,你会发现有一个圆,在这个圆上粒子在不断的向外发散,在发散的过程中粒子的速度是不一样的。而且在发散的过程中,透明度是不断变化的,直到最后完全透明。
好了,总结一下吧。
圆形粒子以不同的粒子速度产生,即随机产生。粒子的透明度降低,直到它们最终消失。粒子向圆心的反方向扩散。写自定义视图的时候,不要一上来就开始,要逐步分析。有时候我们遇到一个复杂的效果,要一帧一帧的分析。
而且我写自定义视图的时候有个习惯,就是要达到一点点效果,而不是一下子达到全部效果。
所以我们的第一步是制造粒子。
粒子的制作首先我们可以知道粒子是有颜色的,但是好像这个效果粒子只有白色,所以把粒子的颜色指定为白色。
那么我们就可以得出结论,质点有一个位置,位置一定是由x和y组成的,那么质点就有一个速度,透明度和半径。
定义粒子我们可以定义一个粒子类:
Class Particle( var x:Float,//X坐标var y:Float,//Y坐标var半径:Float,//半径var速度:Float,//速度var alpha: Int// transparency)复制代码因为我们的这个效果看起来像水波一样的波纹,所以我把自定义视图命名为ripple,也就是涟漪。
让我们定义这个自定义视图
定义自定义视图类dim view (context: context?,attrs: AttributeSet?):View(context,attrs) {//定义一组粒子,private var Particle list = 的可变列表;()//定义画笔var paint = Paint()}从复制代码开始直接产生一个圆形的粒子真的很难。我先考虑如何实现粒子的产生。
先不断产生粒子,再考虑圆形的东西。
而且,产生一堆粒子很麻烦。我先从上到下产生一个粒子。
那么如何产生一个粒子呢?如前所述,一个粒子是一个非常小的点,所以你可以使用画布的drawCircle。
那我们开始吧
覆盖fun onDraw(Canvas:Canvas){ super . onDraw(Canvas)paint . Color = Color。WHITE paint . isantialias = true var Particle = Particle(0f,0f,2f,2f,100)canvas . draw circle(Particle . x,Particle.y,particle.radius,paint)}复制代码进行绘制,会在onDraw方法中完成。我们先做一个新的粒子,然后再画。
其实这个没什么影响。为什么?
我们的背景是白色的,粒子默认是白色的。当然,你看不到。所以我们需要先做一个测试,才能看到效果。在这里,让我们改变背景为黑色。同时,为了测试方便,先将Imageview设置为不可见。然后就看效果了。
是的,就是不行。你什么也看不见。
别担心,慢慢来,听我说。啊,不,听我和你说。
我们这里只花了一个圆,在坐标原点画了一个半径为2的点,可以说很小了。自然看不出来。
什么,你不知道原点在哪里?
棕色部分是我们的屏幕,所以原点是左上角。
我们现在只需要做两件事,要么把点变大,要么改变点的位置。
粒子当然不能变大,所以我们把它放在屏幕中央。
所以我们定义了屏幕中心的坐标,中心x,中心。并在onSizeChanged方法中为它们赋值。
Override fun onsizechanged (w: int,h: int,oldw: int,oldh: int) {super。onsizechanged (w,h,oldw,oldh) centerx = (w/2)。tofloat () centery = (h/2)。tofloat()
覆盖fun onDraw(canvas: Canvas) {...var particle=Particle(centerX,centerY,2f,2f,100)canvas . draw circle(Particle . x,Particle.y,particle.radius,paint)}复制代码。所以,你可以看到这一点。虽然很小,但是聊胜于无。
但是这个时候有人跳出来说你错了。点有什么用?这么年轻,我近视,你让我更瞎了。你是眼镜店的叛徒吗?
添加更多的粒子。好吧,再加几个。但是怎么加呢?效果图是圆的,但是我做不到。我只能先试着加一个横排。让我们看看这是否可能。我们知道,横字的意思是y的值不变,x的值变了。好吧,但是为了避免画线,我们随机增加x的值,看起来也是不规则的。
那么代码应该是这样的。
覆盖fun onDraw(Canvas:Canvas){ super . onDraw(Canvas)paint . Color = Color。WHITE paint . isantialias = true for(I in 0..50){ var Random = Random()var nextX = Random . nextint((centex * 2)。toInt())var Particle = Particle(nextx . to float(),centerY,2f,2f,00)canvas . draw circle(Particle . x,particle.y,particle.radius,paint)}}复制代码。因为centex是屏幕的中心,所以它的值是屏幕宽度的一半。这里,x的值是在屏幕的宽度内随机选择一个值。所以效果是这样的。
看起来不错。
但是总有捣蛋鬼又跳出来说,你会写代码吗?OnDraw方法一直被调用,所以不能定义对象,不知道吗?容易造成频繁的GC,导致内存抖动。更何况你这里有循环。表现还可以吧?
这位小朋友,你说的很对,但是我错了!
的确,ondraw方法不适合定义对象,尤其是对于循环。有一段时间,我们的50粒子看起来并不会耗费太多的性能,但是一旦粒子数量多了,性能成本就会非常大。而且,为了不丢帧,我们需要在16ms内完成画图。如果你不明白这一点,后续我会有专门的性能优化专题。可以关注我~
这里我们测量50个粒子和5000个粒子的绘制时间。
覆盖fun onDraw(Canvas:Canvas){ super . onDraw(Canvas)paint . Color = Color。WHITE paint . isantialias = true var time = measureTimeMillis { for(I in 0..50){ var Random = Random()var nextX = Random . nextint((centex * 2)。toInt())var Particle = Particle(nextx . to float(),centerY,2f,2f,100)canvas . draw circle(Particle . x,particle.y,particle.radius,paint)} } log . I( ;酒窝 , 绘图时间$ time ms )}复制代码的结果如下:50个粒子的绘制时间
500个粒子的绘制时间:
如你所见,它明显超过了16ms。所以我们需要优化。怎么会?很简单,不要在ondraw方法中创建对象就行,那么我们在哪里选择呢?
施工方法可以吗?似乎不可能。这个时候就没有办法得到屏幕的宽度和高度了。嘿嘿onSizeChanged方法决定是你!
将粒子添加到集合override fun onsizechanged (w: int,h: int,oldw: int,oldh:int){ super . onsizechanged(w,h,oldw,oldh) centerX= (w/2)。toFloat() centerY= (h/2)。toFloat()val Random = Random()var nextX = 0 for(I in 0..5000){ nextx = random . nextint((centex * 2))。toint())particle list . add(particle(nextx。tofloat(),center,2f,2f,100))}}复制代码。让我们来看看onDraw方法中的绘图时间:
覆盖fun onDraw(Canvas:Canvas){ super . onDraw(Canvas)paint . Color = Color。WHITE paint . isantialias = true var time = measureTimeMillis { particle list . foreach { canvas . draw circle(I t . x,it.y,it.radius,paint)} } log . I( ;酒窝 , 绘图时间$ time ms )}复制代码
Emmmm,好像是16ms以下,但是太危险了。这一分钟你已经超过了16毫秒。
的确如此,但在现实中,我们并不需要多达5000个粒子。有人问:“如果真的需要呢?”?然后你要看surfaceView。这里就不说了。
我们先回去把粒子数改成50。
现在粒子在那里,是时候移动了。
来,我们想想,我们该怎么办?画面呈圆形展开。我现在做不到。我摔下来应该不难吧?
让我们谈谈,让我们做吧!至于怎么动,那肯定是属性动画了。
定义动画Private var animator = value animator . offer(0f,1f)init { animator . duration = 2000 animator . repeat count = -1 animator . interpolator = linear interpolator()animator . addupdatelistener { update particle(it . animated value as float)invalid()//重绘界面}}复制代码。我在这里,我定义了一个方法,updateparticle,在每次动画更新的时候更新粒子的状态。
updateParticle方法应该做什么?让我们开动脑筋。
如果粒子不断下降,应该是Y值不断增加。嗯,有道理。
让我们实现这个方法。
更新粒子位置private fun update particle(value:float){ particle list . foreach { it . y+= it . speed } } override fun onsize changed(w:int,h: int,Oldw: int,oldh: int) {...animator . start()//别忘了启动动画}复制代码。现在来看看效果如何。
Emmmm看起来有点简陋,但是渲染图中的粒子速度似乎是随机的。我们是同步的。
没关系,我们可以让质点的速度变得随机。我们修改这里的代码来添加粒子。
override fun onSizeChanged(w: Int,h: Int,oldw: Int,oldh:Int){ super . onSizeChanged(w,h,oldw,oldh) centerX = (w / 2)。toFloat() centerY = (h / 2)。toFloat()val Random = Random()var nextX = 0 var speed = 0//为(I in 0)定义速度..50) {nextx = random.nextint((中心x * 2))。toint())speed = random . nextint(10)+5//速度范围从5-15,particle list . add(particle(nextx . to float(),center,2f,speed.tofloat(),100))} animator.start ()}复制代码。就是这个效果,看起来有点像。然而,问题又来了。人的粒子总是分布的。你的粒子怎么不见了?有道理,所以我觉得我们需要设定一个粒子运动的最大距离。一旦超过这个最大距离,让它回到初始位置。
修改粒子类particle的定义(var x:Float,//X坐标var y:Float,//Y坐标var半径:Float,//半径var速度:Float,//速度var alpha: Int,//透明度var maxOffset:Float=300f//最大移动距离)。复制上面的代码,我们添加了一个most但有时我们总是有一个固定的最大移动距离,所以我们在这里设置一个默认值。任何一个粒子想要独一无二,也不是不可能。
有了最大移动距离,我们就要决定,一旦移动距离超过这个值,我们就让它回到起点。这个判断是在哪里做出的?当然是位置更新的地方。
粒子运动距离确定私fun更新particle(value:float){ particle list . foreach { if(it . y-center >;It.maxOffset){ it.y=centerY //重置y的值it.x = random.nextint ((centerx * 2)。toint())。tofloat ()//设置x的随机值it.speed = (random.nextint (10)+5)。to float()//随机设置速度} it.y += it.speed }}复制原代码。我想慢慢来,先随机y,再随机x和速度。
但是我觉得是可以放在一起的,因为一个粒子一旦超过这个最大距离,那么就相当于被回收再生了一个新的粒子,一个新的粒子,它的x,y,速度必然再生了,就可以好看了。
那我们来运行一下,看看效果。
Emmm好像不错?但是,人的粒子看起来很多。没关系。我们在这里设置为300粒子再试一次?
已经很好看了。那么接下来我们该怎么办呢?还存在透明度问题吗?
透明度,大家想想怎么设置吧。首先要尽可能的透明,直到最大值,完全透明。就是这样。透明度和移动距离密切相关。
粒子移动透明私人趣味更新粒子(值:float) {particlelist.foreach {...//设置粒子的透明度it . alpha =((1 F-(it . y-centery)/it . max offset)* 225 f)。toint()...}}覆盖fun raw (canvas: canvas) {...var time = measureTimeMillis { particle list . foreach {//设置画笔paint . alpha = it . alpha canvas . draw circle(it . x,it.y,it.radius,paint)}}...}复制代码,看看效果。..
看起来不错,也很有趣,但是好像不够密集。如果我们把粒子数调整到500会好很多。而且,不知道大家有没有发现,动画刚加载的时候效果很不好。因为所有例子的起点都是一样的,速度必然会一样,所以效果不是很好。添加粒子时只需要初始化Y值。
覆盖fun onSizeChanged(w: Int,h: Int,oldw: Int,oldh:Int){ super . onSizeChanged(w,h,oldw,Oldh)...var nexty = 0ffor (I in 0..500) {...//初始化y的值,这里起点是最小值,最大距离是最大值nexty = random . nextint(400)+centery speed = random . nextint(10)+5 particle list . add(particle(nextx . to float()),Nexty,2f,speed.tofloat(),100)} animator.start ()}复制代码。这样效果会很好,一点问题都没有。现在看来除了不圆之外没什么大问题。然后下一步就是思考如何让它变圆,产生那样的粒子。
定义一个圆。首先,这个圆是圆,但是不能画。
什么意思?
也就是说,圆虽然产生了粒子,但是不能画圆,所以圆只是一条路径。
路径是什么?没错,就是Path。
熟悉的朋友都知道Path可以添加各种路径,比如圆、直线、曲线等。所以我们需要一个圆形的路径。
定义一个路径并添加一个圆。注意,上面提到的性能优化不应该在onDraw中定义。
var Path = Path()override fun onSizeChanged(w:Int,h: Int,oldw: Int,oldh: Int) {...path . add circle(centex,centerY,280f,Path。方向.逆时针)...}复制代码我们在onSizeChanged里面加了一个圈,参数的意思我就不说了,朋友们应该都懂。
既然已经定义了这条路径,但是没有画出来,怎么办?
让我们考虑一下。如果我们想在一个圆内产生粒子,我们需要圆内任意一点的X和Y值吗?有了这些X和Y值,我们能确定粒子的初始位置吗?让我们看看是否有人知道如何确定位置。让知道的朋友举手表决吧。
啊,等了十多分钟,没看到有朋友举手。好像一个都不剩了。
饶我一命!
我说,我说,它其实是类PathMeasure,可以帮助我们得到这条路径上任意一点的位置和方向。如果不知道怎么用,赶紧谷歌一下~或者看我的代码就很好理解了。
private path measure = path measure()//path,用于测量扩散圆某处的X,Y值。扩散圆上某点的private var pos = FloatArray(2) // x,Yprivate val tan = FloatArray(2)//扩散圆上某点的正切复制代码这里我们定义了三个变量,其float是PathMeasure类。第二个和第三个变量是floatarray,pos用于保存圆上某一点的位置信息,其中pos
啧啧,效果和我想象的不一样。一开始好像是一个圆,但不应该像涟漪一样扩散,但你还是在往下掉。
还记得我们之前定义的动画的效果吗,就是x的值不变,y的值不断扩大,那不就是一直在下降吗?所以这里我们需要修改动画规则。
如何修改动画?
想想看,效果图里的动画应该是向外扩散的。扩散是什么意思?就是从它向圆心的反方向移动,对吗?
拍上图就明白了。
这个时候内圈就是我们现在粒子所在的圈。如果在B点有一个粒子,它要扩散的话应该到达H点的位置。
如何得到这个H点的位置?
如果A点是原点,我们就知道了B点此时的位置。分别是X和Y。X=AG,Y=BG .我们也应该能发现,从AB扩展到AH的过程中∠Z始终不变。
同时我们应该可以发现,扩散的过程其实就是圆变大了,于是B的变化移动到了H点。而这个扩大的值意味着圆的半径变大了,也就是半径R = AB,现在半径R=AH。
我们知道AB的值,也就是我们一开始画的圆的半径。但是多少钱啊?
设移动距离偏移量=AH-AB,那么移动距离偏移量是多少?让我们考虑一下。在之前的坠落中,距离等于速度乘以时间吗?但是,我们这里没有时间这个变量,只有循环,在循环中,粒子的Y值是不断加速的。所以我们需要一个可变的偏移值来记录移动的距离,
所以这个偏移量+=速度
那么我们现在知道了偏移量,也就是说AH-AB的值是已知的,我们也知道AB,就可以求出AH的值。
AH = a b+失调
AH知道,∠Z知道,利用三角函数我们可以得到H点的坐标。设初始半径为R=AB
a为原点,cos(≈Z)= AG/AB,sin(≈Z)= BG/ABcos(≈Z)= AG/AB,sin(≈Z)= BG/ABcos(≈Z)= AG/AB。
所以AD
AD = AH∫cos(≈Z)=(AH∫AG)/AB =((R+offset)∫AG)/RAD = AH * cos(≈Z)=(AH * AG)/AB =((R+offset)* AG)/RAD = AH∫cos(≈Z)=(AH∫AG)/AB =((R+offset)∫AG)/R
硬盘(hard disk)
HD = AH≈sin(≈Z)=(AH≈BG)/AB =((R+offset)≈BG)/RHD = AH * sin(≈Z)=(R+offset)* BG)/RHD = AH≈sin(≈Z)=(AH≈BG)/AB =((R+offset)≈BG)/R
按理说是没有问题的。到这个时候,我们已经得到了h的值,但是,注意,这个时候我们是从A点得到的值,而我们手机屏幕的左上角是原点。A点的值此时在程序中是写死的。是centerX和centerY,所以上面的公式要改。
AD =((R+offset)∫(B . X centex))/RAD =((R+offset)*(B . X -centex))/RAD =((R+offset)∫(B . X centex))/R
HD =((R+offset)∫(centerY B . Y))/RHD =((R+offset)*(centerY -B . Y))/RHD =((R+offset)∫(centerY B . Y))/R
注意,此时只是AD和HD的值,只是这两条线段的长度,而不是真正的H点的坐标。H点的坐标应该在A点的基础上增加,即
H.X = AD+centerX =((R+offset)∑(B . X-centerX))/R+centerXH。X = AD+centerX =((R+offset)*(B . x-centerX))/R+centerXH。X = AD+centex =((R+offset)∑(B . X-centex))/R+centex
H.Y = centerY HD = centerY((R+失调)∑(centerY B . Y))/RH。Y = centerY -HD = centerY -((R+偏移量)* (centerY-B.Y) ) / RH。Y = centerY HD = centerY((R+offset)∑(centerY B . Y))/R
而且这个只计算在右半上,也就是第一象限,左半和右下半的计算规则是不一样的。
兄弟,别担心。先听我说。如果你以后不懂,我自己帮你打板凳。
小伙伴们都说上面的公式太复杂了,每个象限都要重新计算一遍。自定义视图有这么复杂吗?
哈哈,不是。我来摇摇你的。
诚然,上面提到了规则,但我们是谁呢?啊,程序员最不应该害怕的就是计算。反正CPU不是我计算的。
我们再来分析一下。这次一定很简单。不要跑!
首先有一个角度z,我们需要记下每个质点的角度,但是这个角度的计算会讨论。
我们先来计算一下左半部分和右半部分区域在右半部分的角度。
Z=(B.X中心)/RZ = (B.X ^ -中心)/RZ =(B . X中心)/R
假设此时∠Z为30,那么也就是说
cos∠Z=0.5cos∠Z = 0.5cos∠Z=0.5
那么h的x值为
H.x = AH∫0.5+center xh。X= AH * 0.5 +centerXH。x = AH∫0.5+centex
但是如果它在左半部分,角度Z
cos∠Z = 0.5 cos∠Z = -0.5 cos∠Z = 0.5
那么h的x值应该是这样计算的。
H.x = centerX AH \0.5h。x = centerX -AH * 0.5h。x = centerX AH \0.5
事实上,本质上
H.x = centex+AH∫cos∠ZH。x = centex+AH * cos∠ZH。x = centerX+AH∫cos∠Z
我们完全不需要考虑左右的问题,因为如果cos∠Z右为正,左为负,直接加就可以了。
我们需要考虑的是上下的问题,也就是Y的问题,毕竟这个正负是基于x的值来计算的,当我们换算成角度的时候,需要根据此时H的Y值是否大于centerY来分别计算。
当h的y值大于centerY时,即h.y < centerY
H.Y = centerY AH \abs(sin∠Z)H . Y = centerY -AH * ABS(sin∠Z)H . Y = centerY AH \abs(sin∠Z)
正相反
H.Y = centerY+AH∫ABS(sin∠Z)H . Y = centerY+AH * ABS(sin∠Z)H . Y = centerY+AH∫ABS(sin∠Z)
这样,随着偏移量的增加,可以随时计算出H点的坐标。
多说无益,代码更直观。
根据上面的描述,我们需要给粒子添加两个属性,一个是移动的距离,一个是粒子的角度。
类粒子(...varoffset: int,//当前移动距离var angle:Double,//粒子角度...)复制代码,并在添加粒子的地方进行修改:
Override fun onsizechanged (w: int,h: int,oldw: int,oldh: int) {...var angle = 0.0for (I in 0..500) {...//反余弦函数可以得到角度值,就是弧度角度= acos ((pos
你现在感觉好多了吗?只是速度有点快,颗粒有点小~没关系。让我们做一些优化工作。比如质点的初始移动距离也取随机值,质点的最大距离也是随机的。这样,我们的效果很好。
override fun onSizeChanged(w: Int,h: Int,oldw: Int,oldh:Int){ super . onSizeChanged(w,h,oldw,oldh) centerX = (w / 2)。toFloat() centerY = (h / 2)。to float()path . add circle(centex,centerY,280f,Path。direction . CCW)path measure . set path(path,false)var nextX = 0f var speed = 0f var nextY = 0f var angle = 0.0 var offSet = 0 var max offSet = 0 for(I in 0..{ path measure . getpostan(I/2000 f * path measure . length,pos,tan) nextX = pos
这是我完成的地址,有一些额外的优化,如X和Y的随机偏移等。朋友们可以手动实现,也可以看看我的代码。当然,我的代码只是为了效果,并没有做更多的优化。不要喷我
这次预览是自定义视图。下一次,我将带您实现自定义视图组。效果是这样的。不是更爽吗?欢迎关注我,一个喜欢自定义视图和NDK开发的小喵~
以上是网易云音乐停不下来的宇宙尘埃效果的详细内容。更多请关注主机参考其他相关文章!
这几篇文章你可能也喜欢:
- 微信小程序翻译功能上线:现已支持18种语言
- 如何使用微信小程序中的车牌号输入法(如何使用微信小程序中的车牌号输入法进行打印)
- 微信小程序无法获取位置信息怎么办?(微信小程序无法获取位置信息)
- 微信小程序如何实现九宫格跳(如何在小程序中配置九宫格抽奖)
- 如何调节微信小程序的亮度?
本文由主机参考刊发,转载请注明:实现不可抗拒的网易云音乐宇宙尘埃特效(网易云音乐特效) https://zhujicankao.com/82484.html
评论前必须登录!
注册