一、效果预览
无中心版本
有中心版本
二、实现
2.1 均匀分布
我们首要解决的问题是计算出粒子运动方向,保证粒子能正常扩散到目标范围和区域,另外还有保证粒子尽可能随机和均匀分布在任意方向。
方法是:
粒子扩散的范围是一个圆的范围内,我们要尽可能利用圆的旋转半径和夹角之间的关系,属于高中数学知识。另外也要控制粒子的数量,防止堆叠过多的问题。
int t = i % 12; double degree = random.nextfloat() * 30 + t * 30; // 12等分圆,没等分都保证产生粒子 // 360 /12 = 30 ,意味着每等分30度区域内需要产生一定的粒子
2.2 速度计算
我们上一篇说过,计算出速度是最难的,要结合场景,这里我们采样计算终点的方式,目的有2个,限制粒子运动出大圆,限制时间。
float minradius = maxradius * 1f / 2f; double radians = math.toradians(degree); int radius = (int) (random.nextfloat() * maxradius / 2f); float x = (float) (math.cos(radians) * (radius + minradius)); float y = (float) (math.sin(radians) * (radius + minradius)); float speedx = (x - 0) / dt; float speedy = (y - 0) / dt;
2.3 颜色
颜色选择自己喜欢的就可以,我喜欢五彩缤纷,所以随机生成
int color = argb(random.nextfloat(), random.nextfloat(), random.nextfloat());
2.4 定义粒子对象
static class star { private final boolean fromcenter; private final int color; private double radians; private float r; float speedx; float speedy; long starttime; path path = new path(); int type = type_quad; public star(float speedx, float speedy, long clocktime, float r, double radians, int color, boolean fromcenter, int type) { this.speedx = speedx; this.speedy = speedy; this.starttime = clocktime; this.r = r; this.radians = radians; this.fromcenter = fromcenter; this.color = color; this.type = type; } public void draw(canvas canvas,paint paint,long clocktime){ } }
2.4 基础骨架
public void drawbase(canvas canvas, paint paint, long clocktime) { long costtime = clocktime - starttime; float dx = speedx * costtime; float dy = speedy * costtime; double currentradius = math.sqrt(dx * dx + dy * dy); paint.setcolor(color); if (currentradius > 0) { double asin = math.asin(r / currentradius); //利用反三角函数计算出切线与圆的夹角 int t = 1; for (int i = 0; i < 2; i++) { double aspectradius = math.abs(math.cos(asin) * currentradius); //切线长度 float ax = (float) (aspectradius * math.cos(radians + asin * t)); float ay = (float) (aspectradius * math.sin(radians + asin * t)); if (fromcenter) { canvas.drawline(0, 0, ax, ay, paint); } else { canvas.drawline(dx / 3, dy / 3, ax, ay, paint); } t = -1; } } canvas.drawcircle(dx, dy, r, paint); }
2.5 进一步优化
public void drawcircleccw(canvas canvas, paint paint, long clocktime) { long costtime = clocktime - starttime; float dx = speedx * costtime; float dy = speedy * costtime; double currentradius = math.sqrt(dx * dx + dy * dy); path.reset(); if (currentradius > 0) { if (fromcenter) { path.moveto(0, 0); } else { path.moveto(dx / 3, dy / 3); } //1、利用反三角函数计算出小圆切线与所有小圆原点与(0,0)点的夹角 double asin = math.asin(r / currentradius); //2、计算出切线长度 double aspectradius = math.abs(math.cos(asin) * currentradius); float axleft = (float) (aspectradius * math.cos(radians - asin)); float ayleft = (float) (aspectradius * math.sin(radians - asin)); path.lineto(axleft, ayleft); float axright = (float) (aspectradius * math.cos(radians + asin)); float ayright = (float) (aspectradius * math.sin(radians + asin)); path.lineto(axright, ayright); path.addcircle(dx, dy, r, path.direction.ccw); } path.close(); paint.setcolor(color); canvas.drawpath(path, paint); }
有点样子了,但是问题是,path动画并没有和粒子圆点闭合,这样就会有问题,后续如果要使用shader着色 (为啥要用shader着色,主要是火焰效果很难画出来,还得借助一些其他工具),必然产生不均匀问题。为了实现开头的效果,最初是计算切线和小圆的夹角让path闭合,但是计算量和难度太大了,直接使用贝塞尔曲线更省事。
public void drawquad(canvas canvas, paint paint, long clocktime) { long costtime = clocktime - starttime; float dx = speedx * costtime; float dy = speedy * costtime; double currentradius = math.sqrt(dx * dx + dy * dy); path.reset(); if (currentradius > 0) { if (fromcenter) { path.moveto(0, 0); } else { path.moveto(dx / 3, dy / 3); } //1、利用反三角函数计算出小圆切线与所有小圆原点与(0,0)点的夹角 double asin = math.asin(r / currentradius); //2、计算出切线长度 double aspectradius = math.abs(math.cos(asin) * currentradius); float axleft = (float) (aspectradius * math.cos(radians - asin)); float ayleft = (float) (aspectradius * math.sin(radians - asin)); path.lineto(axleft, ayleft); float axright = (float) (aspectradius * math.cos(radians + asin)); float ayright = (float) (aspectradius * math.sin(radians + asin)); float cx = (float) (math.cos(radians) * (currentradius + 2 * r)); float cy = (float) (math.sin(radians) * (currentradius + 2 * r)); //如果使用三角函数计算切线可能很复杂,这里使用贝塞尔曲线简化逻辑 path.quadto(cx, cy, axright, ayright); path.lineto(axright, ayright); } path.close(); paint.setcolor(color); canvas.drawpath(path, paint); }
三、全部代码
public class fireworksview extends view implements runnable { private static final long v_sync_time = 30; private final displaymetrics mdm; private textpaint marcpaint; private long displaytime = 500l; //控制时间,防止逃出边界 private long clocktime = 0; private boolean isnextdrawingtimescheduled = false; private textpaint mdrawerpaint = null; private random random; final int maxstartnum = 50; star[] stars = new star[maxstartnum]; private boolean isrefresh = true; public static final int type_base = 1; public static final int type_quad = 2; public static final int type_rect = 3; public static final int type_circle_ccw = 4; public fireworksview(context context) { this(context, null); } public fireworksview(context context, attributeset attrs) { this(context, attrs, 0); } public fireworksview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); mdm = getresources().getdisplaymetrics(); initpaint(); setonclicklistener(new onclicklistener() { @override public void onclick(view v) { startplay(); } }); } public static int argb(float red, float green, float blue) { return ((int) (1 * 255.0f + 0.5f) << 24) | ((int) (red * 255.0f + 0.5f) << 16) | ((int) (green * 255.0f + 0.5f) << 8) | (int) (blue * 255.0f + 0.5f); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); int widthmode = measurespec.getmode(widthmeasurespec); int widthsize = measurespec.getsize(widthmeasurespec); if (widthmode != measurespec.exactly) { widthsize = mdm.widthpixels / 2; } int heightmode = measurespec.getmode(heightmeasurespec); int heightsize = measurespec.getsize(heightmeasurespec); if (heightmode != measurespec.exactly) { heightsize = widthsize / 2; } random = new random(systemclock.uptimemillis()); setmeasureddimension(widthsize, heightsize); } public float dp2px(float dp) { return typedvalue.applydimension(typedvalue.complex_unit_dip, dp, mdm); } public float sp2px(float dp) { return typedvalue.applydimension(typedvalue.complex_unit_sp, dp, mdm); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); int width = getwidth(); int height = getheight(); if (width <= 10 || height <= 10) { return; } int savecount = canvas.save(); int maxradius = math.min(width, height) / 2; canvas.translate(width / 2, height / 2); long clocktime = getclocktime(); if (isrefresh) { float dt = 1000; float r = 5; for (int i = 0; i < maxstartnum; i++) { int t = i % 12; double degree = random.nextfloat() * 30 + t * 30; // 12等分圆 float minradius = maxradius * 1f / 2f; double radians = math.toradians(degree); int radius = (int) (random.nextfloat() * maxradius / 2f); float x = (float) (math.cos(radians) * (radius + minradius)); float y = (float) (math.sin(radians) * (radius + minradius)); float speedx = (x - 0) / dt; float speedy = (y - 0) / dt; int color = argb(random.nextfloat(), random.nextfloat(), random.nextfloat()); stars[i] = new star(speedx, speedy, clocktime, r, radians, color, false, type_quad); } isrefresh = false; } for (int i = 0; i < maxstartnum; i++) { star star = stars[i]; star.draw(canvas, mdrawerpaint, clocktime); } if (!isnextdrawingtimescheduled) { isnextdrawingtimescheduled = true; postdelayed(this, v_sync_time); } canvas.restoretocount(savecount); } @override public void run() { isnextdrawingtimescheduled = false; clocktime += 32; if (clocktime > displaytime) { clocktime = displaytime; } postinvalidate(); } private long getclocktime() { return clocktime; } public void startplay() { clocktime = 0; isrefresh = true; removecallbacks(this); run(); } private void initpaint() { // 实例化画笔并打开抗锯齿 marcpaint = new textpaint(paint.anti_alias_flag); marcpaint.setantialias(true); marcpaint.setstyle(paint.style.stroke); marcpaint.setstrokecap(paint.cap.round); mdrawerpaint = new textpaint(paint.anti_alias_flag); mdrawerpaint.setantialias(true); mdrawerpaint.setstyle(paint.style.fill); mdrawerpaint.setstrokecap(paint.cap.round); } static class star { private final boolean fromcenter; private final int color; private double radians; private float r; float speedx; float speedy; long starttime; path path = new path(); int type = type_quad; public star(float speedx, float speedy, long clocktime, float r, double radians, int color, boolean fromcenter, int type) { this.speedx = speedx; this.speedy = speedy; this.starttime = clocktime; this.r = r; this.radians = radians; this.fromcenter = fromcenter; this.color = color; this.type = type; } public void draw(canvas canvas, paint paint, long clocktime) { switch (type) { case type_base: drawbase(canvas, paint, clocktime); break; case type_rect: drawrect(canvas, paint, clocktime); break; case type_circle_ccw: drawcircleccw(canvas, paint, clocktime); break; case type_quad: drawquad(canvas, paint, clocktime); break; } } public void drawquad(canvas canvas, paint paint, long clocktime) { long costtime = clocktime - starttime; float dx = speedx * costtime; float dy = speedy * costtime; double currentradius = math.sqrt(dx * dx + dy * dy); path.reset(); if (currentradius > 0) { if (fromcenter) { path.moveto(0, 0); } else { path.moveto(dx / 3, dy / 3); } //1、利用反三角函数计算出小圆切线与所有小圆原点与(0,0)点的夹角 double asin = math.asin(r / currentradius); //2、计算出切线长度 double aspectradius = math.abs(math.cos(asin) * currentradius); float axleft = (float) (aspectradius * math.cos(radians - asin)); float ayleft = (float) (aspectradius * math.sin(radians - asin)); path.lineto(axleft, ayleft); float axright = (float) (aspectradius * math.cos(radians + asin)); float ayright = (float) (aspectradius * math.sin(radians + asin)); float cx = (float) (math.cos(radians) * (currentradius + 2 * r)); float cy = (float) (math.sin(radians) * (currentradius + 2 * r)); //如果使用三角函数计算切线可能很复杂,这里使用贝塞尔曲线简化逻辑 path.quadto(cx, cy, axright, ayright); path.lineto(axright, ayright); } path.close(); paint.setcolor(color); canvas.drawpath(path, paint); } public void drawcircleccw(canvas canvas, paint paint, long clocktime) { long costtime = clocktime - starttime; float dx = speedx * costtime; float dy = speedy * costtime; double currentradius = math.sqrt(dx * dx + dy * dy); path.reset(); if (currentradius > 0) { if (fromcenter) { path.moveto(0, 0); } else { path.moveto(dx / 3, dy / 3); } //1、利用反三角函数计算出小圆切线与所有小圆原点与(0,0)点的夹角 double asin = math.asin(r / currentradius); //2、计算出切线长度 double aspectradius = math.abs(math.cos(asin) * currentradius); float axleft = (float) (aspectradius * math.cos(radians - asin)); float ayleft = (float) (aspectradius * math.sin(radians - asin)); path.lineto(axleft, ayleft); float axright = (float) (aspectradius * math.cos(radians + asin)); float ayright = (float) (aspectradius * math.sin(radians + asin)); path.lineto(axright, ayright); path.addcircle(dx, dy, r, path.direction.ccw); } path.close(); paint.setcolor(color); canvas.drawpath(path, paint); } public void drawbase(canvas canvas, paint paint, long clocktime) { long costtime = clocktime - starttime; float dx = speedx * costtime; float dy = speedy * costtime; double currentradius = math.sqrt(dx * dx + dy * dy); paint.setcolor(color); if (currentradius > 0) { double asin = math.asin(r / currentradius); //利用反三角函数计算出切线与圆的夹角 int t = 1; for (int i = 0; i < 2; i++) { double aspectradius = math.abs(math.cos(asin) * currentradius); //切线长度 float ax = (float) (aspectradius * math.cos(radians + asin * t)); float ay = (float) (aspectradius * math.sin(radians + asin * t)); if (fromcenter) { canvas.drawline(0, 0, ax, ay, paint); } else { canvas.drawline(dx / 3, dy / 3, ax, ay, paint); } t = -1; } } canvas.drawcircle(dx, dy, r, paint); } public void drawrect(canvas canvas, paint paint, long clocktime) { long costtime = clocktime - starttime; float dx = speedx * costtime; float dy = speedy * costtime; paint.setcolor(color); rectf rectf = new rectf(dx - r, dy - r, dx + r, dy + r); canvas.drawrect(rectf, paint); // canvas.drawcircle(dx,dy,r,paint); } } }
四、总结
本篇我们大量使用了三角函数、反三角函数,因此一定要掌握好数学基础。
以上就是基于android实现烟花效果的详细内容,更多关于android烟花效果的资料请关注代码网其它相关文章!
发表评论