前言
粒子动画经常用于大画幅的渲染效果,实际上难度并不高,但是在使用粒子动画时,必须要遵循的一些要素,主要是:
- 起点
- 矢量速度
- 符合运动学公式
起点之所以重要是因为其实位置决定粒子出现的位置,矢量速度则决定了快慢和方向,运动学公式属于粒子动画的一部分,当然不是物理性的,毕竟平面尺寸也就那么长,这里的物理学公式使得画面更加丝滑而无跳动感觉。
本篇将实现下面的效果
注意:gif图有些卡,实际上流畅很多
本篇效果实现
本篇效果是无数圆随机产生然后渐渐变大并外旋,另外也有雨滴,这里的雨滴相对简单一些。
首先定义粒子对象
定义粒子对象是非常重要的,绝大部分倾下粒子本身就是需要单独控制的,因为每个粒子的轨迹都是有所差别的。
定义圆圈粒子
private static class Circle { float x; float y; int color; float radius; Circle(float x, float y, float radius) { reset(x, y, radius); } private void reset(float x, float y, float radius) { this.x = x; this.y = y; this.radius = radius; this.color = Color.rgb((int) (Math.random() * 256), (int) (Math.random() * 256), (int) (Math.random() * 256)); } }
定义雨滴
private static class RainDrop { float x; float y; RainDrop(float x, float y) { this.x = x; this.y = y; } }
定义粒子管理集合
private ArrayList<Circle> mParticles; private ArrayList<RainDrop> mRainDrops; private long mLastUpdateTime; //记录执行时间
生成粒子对象
- 生成雨滴是从顶部屏幕意外开始,而y = -50f值是雨滴的高度决定。
- 圆圈是随机产生,在中心位置圆圈内。
// 创建新的雨滴 if (mRainDrops.size() < 80) { int num = getWidth() / padding; double nth = num * Math.random() * padding; double x = nth + padding / 2f * Math.random(); RainDrop drop = new RainDrop((float) x, -50f); mRainDrops.add(drop); } // 创建新的粒子 if (mParticles.size() < 100) { float x = (float) (getWidth() / 2f - radius + 2*radius * Math.random()); float y = (float) (getHeight()/2f - radius + 2*radius * Math.random() ); Circle particle = new Circle(x, y,5); mParticles.add(particle); }
绘制雨滴
雨滴的绘制非常简单,调用相应的canvas方法即可
// 绘制雨滴 mPaint.setColor(Color.WHITE); for (RainDrop drop : mRainDrops) { canvas.drawLine(drop.x, drop.y, drop.x, drop.y + 20, mPaint); } // 绘制粒子 for (Circle particle : mParticles) { mPaint.setColor(particle.color); canvas.drawCircle(particle.x, particle.y, particle.radius, mPaint); }
更新粒子位置
雨滴的更新相对简单,但是圆圈的旋转是一个难点,一个重要的问题是如何旋转粒子的,其实有很多方法,其中最笨的方法是旋转Canvas坐标系,底层有很多矩阵计算,但是这个似乎使用Math.atan2(y,x)显然更加方便,我们只需要在当前角度加上偏移量就能旋转。
float angle = (float) Math.atan2(dy, dx) + deltaTime * 0.65f;
下面是完整的更新逻辑
// 更新雨滴位置 Iterator<RainDrop> rainIterator = mRainDrops.iterator(); while (rainIterator.hasNext()) { RainDrop drop = rainIterator.next(); if (drop.y > getHeight() + 50) { int num = getWidth() / padding; double nth = num * Math.random() * padding; double x = nth + padding * Math.random(); drop.x = (float) (x); drop.y = -50; } else { drop.y += 20; } } // 更新粒子位置 long currentTime = System.currentTimeMillis(); float deltaTime = (currentTime - mLastUpdateTime) / 1000f; mLastUpdateTime = currentTime; float centerX = getWidth() / 2f; float centerY = getHeight() / 2f; Iterator<Circle> iterator = mParticles.iterator(); while (iterator.hasNext()) { Circle particle = iterator.next(); float dx = particle.x - centerX; float dy = particle.y - centerY; float distance = (float) Math.sqrt(dx * dx + dy * dy) + 4.5f;// 增加偏移 float angle = (float) Math.atan2(dy, dx) + deltaTime * 0.5f; particle.radius += 1f; particle.x = centerX + (float) Math.cos(angle) * distance; particle.y = centerY + (float) Math.sin(angle) * distance; if (particle.radius > 10) { int maxRadius = 100; float fraction = (particle.radius - 10) / (maxRadius - 10); if (fraction >= 1) { fraction = 1; } particle.color = argb((int) (255 * (1 - fraction)), Color.red(particle.color), Color.green(particle.color), Color.blue(particle.color)); } if (Color.alpha(particle.color) == 0) { float x = (float) (getWidth() / 2f - radius + 2* radius * Math.random()); float y = (float) (getHeight()/2f - radius + 2*radius * Math.random() ); particle.reset(x,y, 5); } }
粒子刷新
其实刷新机制我们以前经常使用,调用postInvalidate即可,本身就是View自身的方法。
总结
本篇主要内容总体上就是这些,下面是全部代码逻辑
public class VortexView extends View { private Paint mPaint; private ArrayList<Circle> mParticles; private ArrayList<RainDrop> mRainDrops; private long mLastUpdateTime; private int padding = 20; public VortexView(Context context) { super(context); mPaint = new Paint(); mParticles = new ArrayList<>(); mRainDrops = new ArrayList<>(); mLastUpdateTime = System.currentTimeMillis(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); float radius = Math.min(getWidth(), getHeight()) / 3f; // 创建新的雨滴 if (mRainDrops.size() < 80) { int num = getWidth() / padding; double nth = num * Math.random() * padding; double x = nth + padding / 2f * Math.random(); RainDrop drop = new RainDrop((float) x, -50f); mRainDrops.add(drop); } // 创建新的粒子 if (mParticles.size() < 100) { float x = (float) (getWidth() / 2f - radius + 2*radius * Math.random()); float y = (float) (getHeight()/2f - radius + 2*radius * Math.random() ); Circle particle = new Circle(x, y,5); mParticles.add(particle); } // 绘制雨滴 mPaint.setColor(Color.WHITE); for (RainDrop drop : mRainDrops) { canvas.drawLine(drop.x, drop.y, drop.x, drop.y + 20, mPaint); } // 绘制粒子 for (Circle particle : mParticles) { mPaint.setColor(particle.color); canvas.drawCircle(particle.x, particle.y, particle.radius, mPaint); } // 更新雨滴位置 Iterator<RainDrop> rainIterator = mRainDrops.iterator(); while (rainIterator.hasNext()) { RainDrop drop = rainIterator.next(); if (drop.y > getHeight() + 50) { int num = getWidth() / padding; double nth = num * Math.random() * padding; double x = nth + padding * Math.random(); drop.x = (float) (x); drop.y = -50; } else { drop.y += 20; } } // 更新粒子位置 long currentTime = System.currentTimeMillis(); float deltaTime = (currentTime - mLastUpdateTime) / 1000f; mLastUpdateTime = currentTime; float centerX = getWidth() / 2f; float centerY = getHeight() / 2f; Iterator<Circle> iterator = mParticles.iterator(); while (iterator.hasNext()) { Circle particle = iterator.next(); float dx = particle.x - centerX; float dy = particle.y - centerY; float distance = (float) Math.sqrt(dx * dx + dy * dy) + 3.5f;// 增加偏移 float angle = (float) Math.atan2(dy, dx) + deltaTime * 0.65f; particle.radius += 1f; particle.x = centerX + (float) Math.cos(angle) * distance; particle.y = centerY + (float) Math.sin(angle) * distance; if (particle.radius > 10) { int maxRadius = 100; float fraction = (particle.radius - 10) / (maxRadius - 10); if (fraction >= 1) { fraction = 1; } particle.color = argb((int) (255 * (1 - fraction)), Color.red(particle.color), Color.green(particle.color), Color.blue(particle.color)); } if (Color.alpha(particle.color) == 0) { float x = (float) (getWidth() / 2f - radius + 2* radius * Math.random()); float y = (float) (getHeight()/2f - radius + 2*radius * Math.random() ); particle.reset(x,y, 5); } } Collections.sort(mParticles, comparator); // 使view无效从而重新绘制,实现动画效果 invalidate(); } Comparator comparator = new Comparator<Circle>() { @Override public int compare(Circle left, Circle right) { return (int) (left.radius - right.radius); } }; public static int argb( int alpha, int red, int green, int blue) { return (alpha << 24) | (red << 16) | (green << 8) | blue; } private static class Circle { float x; float y; int color; float radius; Circle(float x, float y, float radius) { reset(x, y, radius); } private void reset(float x, float y, float radius) { this.x = x; this.y = y; this.radius = radius; this.color = Color.rgb((int) (Math.random() * 256), (int) (Math.random() * 256), (int) (Math.random() * 256)); } } private static class RainDrop { float x; float y; RainDrop(float x, float y) { this.x = x; this.y = y; } } }
以上就是Android实现粒子漩涡动画的详细内容,更多关于Android粒子漩涡的资料请关注其它相关文章!