主机参考:VPS测评参考推荐/专注分享VPS服务器优惠信息!若您是商家可以在本站进行投稿,查看详情!此外我们还提供软文收录、PayPal代付、广告赞助等服务,查看详情! |
我们发布的部分优惠活动文章可能存在时效性,购买时建议在本站搜索商家名称可查看相关文章充分了解该商家!若非中文页面可使用Edge浏览器同步翻译!PayPal代付/收录合作 |
微信小程序开发教程专栏今天教你实现网易云音乐宇宙尘埃特效,手把手教。
前言前段时间女朋友在用网易云音乐的时候看到了一个宇宙尘埃特效。她说很漂亮,想让我给她开个VIP。
笑话,作为一个程序员,自己怎么就体会不到呢!好一个VIP!!
什么女朋友?程序员有吗?我只关心特效的实现!
现在是0202,大部分安卓开发应该都是老式的。如果你仍然不精通定制视图,它将没有任何意义。自定义视图可以说是Android开发中必须掌握的一个点,无论是初级、中级还是高级。
不然UI一不小心设计的太爽了,你不是要和他打一架吗?你不想成为下图中的那个人吗?
所以,自定义视图的重要性就不用我多说了。本文针对有自定义视图基础知识,但苦于没有好的项目模仿,或者看到很酷的效果,没有想法,不知道如何下手的人。恭喜你,我带你一步步分析效果,然后用代码实现。
我知道没有地图你骗不了任何人。先放图,我们来看看最后的效果。
Ps:为了加载的更快,gif被压缩压缩,让每个人都有一个清晰的大脑。
Ps2:朋友有好的gif压缩网站可以推荐一波。
咳咳,虽然画质堪比AV,但还是能看出来效果很好。所以今天我就带我的朋友来从头到尾体会一下这个效果。
特效的分析从动画开始,可以分为两个部分,一个是里面不停旋转的圆形画面,一个是外面不停扩散的粒子的动态效果。
让我们从易到难完成它。毕竟柿子要摘,要挤。
另外,由于本文侧重于自定义视图,所以不使用ViewGroup实现图片和粒子动力学的结合。而是采用了单独的布局。这样做的好处是,你可以只专注于粒子动态效果的实现,而不用考虑测量、布局等等。
至于自定义ViewGroup,我会在下一篇文章中带领大家实现一个非常非常酷的效果。
加载图片。我们先观察一下。首先,这是一张圆图。其次,它一直在转。
咳咳,先别骂,让我说完。
我们将使用Glide来实现圆形图片。其实自定义视图实现也是可以的,只是我们的重点是粒子特效。
首先,定义一个ImageView。
& lt?xml版本= & quot1.0 & quot编码= & quotut F-8 & quot;?& gt& ltrelative layout xmlns:Android = & quot;http://schemas.android.com/apk/res/android" xmlns:app = & quot;http://schemas.android.com/apk/res-auto" xmlns:tools = & quot;http://schemas.android.com/tools" Android:id = & quot;@+id/root layout & quot;Android:layout _ width = & quot;匹配父母& quotAndroid:layout _ height = & quot;匹配父母& quotAndroid:fitsSystemWindows = & quot;true & quot& gt& ltImageView Android:id = & quot;@+id/music _ avatar & quot;Android:layout _ centerInParent = & quot;true & quotAndroid:layout _ width = & quot;178dp & quotAndroid:layout _ height = & quot;178dp & quot/& 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?){demobinding。音乐艺术家。setimagedrawable (resource)}}}复制代码,这样我们就可以用Glide加载一个圆形图片了。
旋转图片。有了图,下一步应该是旋转。
那我们开始转吧。
旋转是如何实现的?我想我不用多说了。很多朋友都知道是动漫。
没错,就是动漫。这里我们用属性动画来实现。
定义一个属性动画,为图片设置一个click事件,使其旋转。
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()life cycle scope . launch(dispatchers . main){ loadimage()//添加click事件,并启动动画demobinding。音乐艺术家。setonclicklistener { rotate animator。Start ()}}复制代码。这些都是小儿科。我相信面对电视机前的观众,啊不,是口误。
相信朋友们都很熟悉了,那就开始今天的重头戏,这个粒子动画吧。
粒子动画其实很久以前看粒子动画的时候,我也很好奇这些很酷的粒子动画是怎么实现的。那时候我一点概念都没有。
尤其是看到一些图片的时候,变成了一堆颗粒,掉了下来,然后又从颗粒变成了图片,让我觉得不正常。
其实一点都不神奇。
首先,我们需要知道什么是位图。什么是位图?
在数学中,有几个概念,如点、线、面。一个点很好理解,就是一个点。线是由一堆点组成的,面类似于一堆线。本质上,一个曲面是由无数个点组成的。
但是这跟位图和今天的粒子动画有什么关系呢?
位图可以简单理解为图片。这张图片是一架飞机吗?而平面是由一堆点组成的,这里叫做像素点。所以位图是由一堆像素组成的。有趣的是,这些像素是有颜色的。当这些像素足够小,你离得足够远,你看起来就像一幅完整的画。
现实中,这样的例子很多。举行一些活动时,一个人用不同的颜色按顺序站在广场上。如果空中有一架无人机,你可以看到它是一幅画。像这样
所以当一幅画被分解成一束粒子的时候,实际上就是把位图中的所有像素点都取过来,然后改变它们的位置。如果要用一堆粒子拼凑出一幅图,只需要知道这些粒子的顺序,排列整齐自然就是一幅图。
离题很远,其实和今天的效果无关,只是为了让你更好的理解粒子动画的本质。
粒子动画分析我们先来观察一下这个特效,你会发现有一个圆,在这个圆上粒子在不断的向外发散,在发散的过程中粒子的速度是不一样的。而且在发散的过程中,透明度是不断变化的,直到完全透明。
好了,总结一下。
圆形生产粒子的粒子速度是不同的,也就是随机的。粒子的透明度不断降低,直到最后消散。粒子向圆心的反方向扩散。写自定义视图的时候,不要一下子就开始,要逐步分析。有时候我们遇到一个复杂的效果,要一帧一帧的分析。
而且我写自定义视图的时候,有一个习惯就是达到一点点效果,不会一下子达到全部效果。
所以我们的第一步是制造粒子。
首先我们可以知道粒子是有颜色的,但是好像效果粒子只有白色,所以把粒子颜色指定为白色。
那么我们就可以得出结论,质点有一个位置,位置一定是由x和y组成的,那么质点就有一个速度,透明度和半径。
定义粒子我们可以定义一个粒子类:
Class Particle( var x:Float,//X坐标var y:Float,//Y坐标var半径:Float,//半径var速度:Float,//速度var alpha: Int// transparency)复制代码因为我们的效果看起来像水波一样的波纹,我把自定义视图命名为ripple,它是暗淡的。
让我们定义这个自定义视图
定义一个自定义视图类dimpleview (context: context?,attrs: AttributeSet?):View(context,attrs) {//定义一组粒子Private var particle list =可变列表
先不停地产生粒子,再去想圆。
而且产生一堆粒子更麻烦。我将首先从上到下产生一个粒子。
那么如何产生一个粒子呢?之前说过,一个粒子是一个很小的点,所以可以用canvas的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的点,可以说很小了。你自然看不出来。
什么,你不知道原点在哪里?
棕色部分是我们的屏幕,所以原点是左上角。
现在我们只需要做两件事,要么把点做大,要么改变点的位置。
粒子当然不能变大,所以我们把它放在屏幕中央。
所以我们定义了屏幕中心的坐标。并在onSizeChanged方法中为它们赋值
Override fun onsizechanged (w: int,h: int,oldw: int,oldh: int) {super。onsizechanged (w,h,oldw,oldh)中心x = (w/2)。Tofloat () Center = (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,100) canvas。drawcircle (particle.x,particle.y,particle.radius,paint) }}复制代码由于centex是屏幕的中心,所以它的值是屏幕宽度的一半。在这种情况下,X的值是在屏幕宽度内随机选择一个值。所以效果看起来像这样
效果看起来不错。
但是总有捣蛋鬼又跳出来说,你会写代码吗?onDraw方法一直被调用,不能定义对象,不知道吗?容易造成频繁的GC,造成内存抖动。况且你这里还有个循环,表演能起作用吗?
你对这位小朋友的看法完全正确,但我错了!
的确,不适合在ondraw方法中定义对象,尤其是在for循环中。有一段时间,看起来我们的50粒子在性能上花费不多,但是一旦粒子数量多了,性能成本就很大了。而且为了不掉帧,我们需要在16 ms内完成画图,如果你不明白这个,我以后会有性能优化的专题,你可以关注我~
这里我们测量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(& quot;酒窝& quot,& quot绘图时间$ time ms & quot)}复制代码的结果如下:50个粒子的绘制时间。
5000个粒子的绘制时间:
如你所见,它明显超过16毫秒,所以我们需要优化。怎么会?很简单,就是不要在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((中心x * 2)。Toint ()) particlelist。添加(粒子(nextx。Tofloat(),centery,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(& quot;酒窝& quot,& quot绘图时间$ time ms & quot)}复制代码
Emmmm,好像是16ms以下,但是太危险了。你现在已经超过16毫秒了。
的确如此,但在现实中,我们不需要5000个这么多的粒子。有人问,如果真的有必要呢?然后你要看surfaceView。这里就不说了。
我们先回去把粒子数改成50。
现在粒子在那里,是时候实现移动的效果了。
我们走吧。让我们考虑一下。我们做什么呢效果图呈圆形扩散。我现在做不到。我摔倒应该不难吧?
说动就动!我们开始吧!至于怎么动,那肯定是属性动画了。
定义动画私有VarAnimator = ValueAnimator。Offload (0f,1f)init { Animator . duration = 2000 Animator . repeat count = -1 Animator . interpolator = linear interpolator()Animator。AddupdateListener { update particle(it。动画值为float)invalid()//重绘界面}}复制代码。我在这里,定义一个方法,Update particle,在每次动画更新的时候更新粒子的状态。
updateParticle方法应该做什么?大家动动脑筋想想吧。
如果粒子不断下落,应该是y的值不断增加。嗯,有道理。
让我们实现这个方法。
更新粒子位置私fun更新粒子(值:float){粒子列表。foreach { it . y+= it . speed } } override fun onsize changed(w:int,h: int,Oldw: int,oldh: int) {...动画师。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)。to float()val Random = Random()var nextX = 0 var speed = 0//为(I in 0.50){ nextX = Random . nextint((center x * 2)定义一个速度。Toint())速度= random.nextint (10)+5//速度从5-15不等。粒子列表。添加(粒子(nextx。Tofloat(),居中,2f,速度。Tofloat(),100))}动画师。Start ()}复制代码。就是这个效果,看起来有点像。然而,问题又来了。人的粒子总是在发射。为什么你的粒子不见了?有道理,所以我觉得我们需要设定一个粒子运动的最大距离。一旦超过这个最大距离,我们会让它回到原来的位置。
修改粒子类particle的定义(var x:Float,//X坐标var y:Float,//Y坐标var半径:Float,//半径var速度:Float,//速度var alpha: Int,//透明度var maxOffset:Float=300f//最大移动距离)。复制上面的代码。我们增加了最大移动距离。但有时最大移动距离是固定的,所以我们在这里设置一个默认值。任何一个粒子想要独立,也不是不可能。
有了最大移动距离,我们就要决定,一旦移动距离超过这个值,我们就让它回到起点。这个判断是在哪里做出的?当然是位置更新的地方。
粒子运动距离的确定私fun更新粒子(值:float){粒子列表。foreach { if(it . y-centery >;It.maxOffset){ It.y=centerY //重置y值it。x =随机。nextint((中心x * 2)。Toint())。Tofloat ()//随机设置x值it。速度=(随机。Nextint (10)+5)。to float()//随机设置速度} it.y += it.speed }}复制代码本来,我想慢慢来,先随机y,在随机x和速度。
但是我觉得可以放在一起说,因为一个粒子一旦超过这个最大距离,就相当于被回收再生成一个新的粒子,而一个新的粒子必须在X,Y,速度上再生,这样才好看。
那我们来运行一下,看看效果。
Emmm好像还不错吧?但是其他的看起来粒子很多。没关系。让我们用300个粒子再试一次。
看起来足够好了。那么接下来我们该怎么办呢?还存在透明度问题吗?
如果有透明度,如何设置?首先,应该尽量透明一些,直到最大化,完全透明。在这里。透明度和移动距离密切相关。
粒子移动透明私人趣味更新粒子(值:float){粒子列表。foreach {...//设置粒子的透明度it . alpha =((1 F-(it . y-centery)/it . max offset)* 225 f)。Toint()...}}覆盖fun ondraw (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)...Varnexty = 0ffor (iin0...500) {...//初始化y的值,其中起点是最小值。最大距离取为最大Nexty = random。Nextint (400)+Centery速度=随机。Nextint (10)+5粒子列表。添加(粒子(Nextx。Tofloat()),Nexty,2f,speed。Tofloat(),100)}动画师。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值,才能确定粒子的初始位置?看看是否有人知道如何确定位置。知道的请举手。
啊,等了十多分钟,没看到有朋友举手。好像没有人。
原谅我,英雄!
我说,我说,它其实是类PathMeasure,可以帮助我们得到这条路径上任意一点的位置和方向。如果不知道怎么用,赶紧谷歌一下~或者看看我的代码,很好理解。
private val path measure = path measure()//path,用于测量扩散圆上某一点的x和y值。扩散圆上某点的private var pos = FloatArray(2) // X。Yprivate val tan = FloatArray(2)//复制与扩散圆上一点相切的代码这里我们定义了三个变量,第一个是PathMeasure类,第二个和第三个变量是一个float数组。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 . yh . y = centerY HABs(sin∠z)h . y = centerY -ah * ABS(sin∠z)h . y = centerY HABs(sin∠z)。
正相反
H.Y = centerY+AH∫ABS(sin∠Z)H . Y = centerY+AH * ABS(sin∠Z)H . Y = centerY+AH∫ABS(sin∠Z)
这样,随着偏移量的增加,可以随时计算出H点的坐标。
多说无益,还是代码更直观。
根据上面的描述,我们需要给质点添加两个性质,一个是运动距离,一个是质点的角度。
类粒子(...var offset: int,//当前移动距离var angle:Double,//粒子角度...)复制代码并在添加粒子的地方进行修改:
Override fun onsizechanged (w: int,h: int,oldw: int,oldh: int) {...对于(iin0.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的随机偏移等。朋友们可以手动实现或者看看我的代码。当然,我的代码只是为了达到效果,并没有更多的优化。别喷我:DimpleView。
这次预览是自定义视图。下次我就带大家实现一个自定义的ViewGroup,效果是这样的。是不是更爽?欢迎关注我,喜欢自定义视图和NDK开发的小喵~
这几篇文章你可能也喜欢:
- 微信小程序翻译功能上线:现已支持18种语言
- 如何使用微信小程序中的车牌号输入法(如何使用微信小程序中的车牌号输入法进行打印)
- 微信小程序无法获取位置信息怎么办?(微信小程序无法获取位置信息)
- 微信小程序如何实现九宫格跳(如何在小程序中配置九宫格抽奖)
- 如何调节微信小程序的亮度?
本文由主机参考刊发,转载请注明:实现网易云音乐宇宙尘特效(网易云宇宙尘动态效果) https://zhujicankao.com/121286.html
评论前必须登录!
注册