一、效果预览
无中心版本

有中心版本

二、实现
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烟花效果的资料请关注代码网其它相关文章!
发表评论