GitHub地址
原创文章,转载请注明出处
萤火虫飞舞粒子效果
本项目中我提供了两种方案,最终呈现的效果如下:
先奉上GitHub地址戳这里,有兴趣的同鞋star一下咯
实现原理
Android的粒子效果、粒子动画,已经有很多开源的轮子了。作为一个坚定的轮子主义者,我google了大半天,却没有找到这种类似于萤火虫飞舞的效果。只好自己来实现这种效果。
相比较普通的View,SurfaceView更加适合这种不断变化的画面,所以选择SurfaceView来实现。现在把思路再重新梳理一下:
- 大小不同的粒子在区域内随机分布
- 粒子做无规则运动,然后消失
粒子区域内随机分布
这个简单,我们在callBack的方法内直接循环生成一个粒子的数组即可。方位的话使用Random即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| if (mCircles.size() == 0) { for (int i = 0; i < MAX_NUM; i++) { FloatParticleLine f = new FloatParticleLine(getF() * mMeasuredWidth, getF() * mMeasuredHeight, mMeasuredWidth, mMeasuredHeight); f.setRadius(mRandom.nextInt(2) + 1.2f); mCircles.add(f); } } private float getF() { float v = mRandom.nextFloat(); if (v < 0.2f) { return v + 0.2f; } else if (v >= 0.85f) { return v - 0.2f; } else { return v; } }
|
getF()方法是限制在区域内取值,mMeasuredWidth、mMeasuredHeight为SurfaceView的宽和高。
这里的宽和高在粒子对象FloatParticleLine,内会用到。
然后我们在创建一个线程,在run()方法内做无线循环的绘制即可,为了避免无意义的绘制,可以使用Thread.sleep方法来控制帧数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| while (isRun) { try { mCanvas = mHolder.lockCanvas(null); if (mCanvas != null) { synchronized (mHolder) { // 清屏 mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (FloatParticleLine circle : mCircles) { circle.drawItem(mCanvas); } // 控制帧数 Thread.sleep(25); } } } catch (Exception e) { e.printStackTrace(); } finally { if (mCanvas != null) { mHolder.unlockCanvasAndPost(mCanvas); } } }
|
isRun的变量我们会在SurfaceView内callBack的surfaceDestroyed方法中置为false
粒子做无规则运动
其实看到这种粒子效果,首先应该想到的就是Canvas了。
在SurfaceView里就是通过不断地循环调用FloatParticleLine类的drawItem()方法来实现粒子的运动。我第一种方案的实现,就是每一个粒子在被创建出来的时候,就随机选择一个方向开始运动,滑过一定的轨迹之后让其消失就好了。
至于怎么选择随机方向,我这里的做法是,分别随机生成一个x和y轴上的递增或者递减的数值,然后每次在前一次绘制的基础上,x和y分别递增递减,直到运动到屏幕边缘或者是规定的运动距离满足了再消失即可。
1 2 3 4 5 6 7 8 9 10 11 12 13
| //随机生成参数 private void setRandomParm() { // 2017/5/2-上午10:47 x和y的方向 mIsAddX = mRandom.nextBoolean(); mIsAddY = mRandom.nextBoolean();
// 2017/5/2-上午10:47 x和y的取值 mDisX = mRandom.nextInt(2) + 0.2f; mDisY = mRandom.nextInt(2) + 0.3f;
// 2017/5/2-上午10:47 内部区域的运动最远距离 mDistance = mRandom.nextInt((int) (0.25f * mWidth)) + (0.125f * mWidth); }
|
绘制图形:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public void drawItem(Canvas canvas) { if (mX == mStartX) { mPaint.setAlpha(ALPHA_MAX); } //绘制 canvas.drawCircle(mX += getPNValue(mIsAddX, mDisX), mY += getPNValue(mIsAddY, mDisY), mRadius, mPaint); //内部区域运动到一定距离消失 if (judgeInner()) { float gapX = Math.abs(mX - mStartX); float ratio = 1 - (gapX / mDistance); mPaint.setAlpha((int) (255 * ratio)); mRadius = mStartRadius * ratio; if (gapX >= mDistance || mY - mStartY >= mDistance) { resetDisXY(); return; } return; } //外部区域运动到屏幕边缘消失 if (judgeOutline()) { resetDisXY(); } }
private void resetDisXY() { setRandomParm();
mPaint.setAlpha(0); mX = mStartX; mY = mStartY; mRadius = mStartRadius; }
|
judgeInner()和judgeOutline()是判断区域的方法,内部区域的点和外部区域的店消失时机不同
在透明度为0也就是粒子消失时,让粒子回到原点,再重新选择一个方向,进行下一步运动轨迹。
方案二粒子做的运动是贝塞尔曲线,函数实在网上找到的一个函数。每当粒子做完一次曲线运动后,再随机生成一段新的贝塞尔曲线即可。
思路和方案一的思路都是一样的,无非就是运动的轨迹不同而已。
总结
做完之后回头再看,发现这个项目的原理其实并不难,可以说是简单了。但刚开始起步的时候真的还是比较懵的,原因就是没有思路。
所以做任何效果,思路最重要。