基于Android實(shí)現(xiàn)煙花效果
一、效果預(yù)覽
無(wú)中心版本

有中心版本

二、實(shí)現(xiàn)
2.1 均勻分布
我們首要解決的問(wèn)題是計(jì)算出粒子運(yùn)動(dòng)方向,保證粒子能正常擴(kuò)散到目標(biāo)范圍和區(qū)域,另外還有保證粒子盡可能隨機(jī)和均勻分布在任意方向。

方法是:
粒子擴(kuò)散的范圍是一個(gè)圓的范圍內(nèi),我們要盡可能利用圓的旋轉(zhuǎn)半徑和夾角之間的關(guān)系,屬于高中數(shù)學(xué)知識(shí)。另外也要控制粒子的數(shù)量,防止堆疊過(guò)多的問(wèn)題。
int t = i % 12; double degree = random.nextFloat() * 30 + t * 30; // 12等分圓,沒(méi)等分都保證產(chǎn)生粒子 // 360 /12 = 30 ,意味著每等分30度區(qū)域內(nèi)需要產(chǎn)生一定的粒子
2.2 速度計(jì)算
我們上一篇說(shuō)過(guò),計(jì)算出速度是最難的,要結(jié)合場(chǎng)景,這里我們采樣計(jì)算終點(diǎn)的方式,目的有2個(gè),限制粒子運(yùn)動(dòng)出大圓,限制時(shí)間。
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 顏色
顏色選擇自己喜歡的就可以,我喜歡五彩繽紛,所以隨機(jī)生成
int color = argb(random.nextFloat(), random.nextFloat(), random.nextFloat());
2.4 定義粒子對(duì)象
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 基礎(chǔ)骨架
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);
//利用反三角函數(shù)計(jì)算出切線與圓的夾角
int t = 1;
for (int i = 0; i < 2; i++) {
double aspectRadius = Math.abs(Math.cos(asin) * currentRadius); //切線長(zhǎng)度
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 進(jìn)一步優(yōu)化
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、利用反三角函數(shù)計(jì)算出小圓切線與所有小圓原點(diǎn)與(0,0)點(diǎn)的夾角
double asin = Math.asin(r / currentRadius);
//2、計(jì)算出切線長(zhǎng)度
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);
}

有點(diǎn)樣子了,但是問(wèn)題是,Path動(dòng)畫(huà)并沒(méi)有和粒子圓點(diǎn)閉合,這樣就會(huì)有問(wèn)題,后續(xù)如果要使用Shader著色 (為啥要用Shader著色,主要是火焰效果很難畫(huà)出來(lái),還得借助一些其他工具),必然產(chǎn)生不均勻問(wèn)題。為了實(shí)現(xiàn)開(kāi)頭的效果,最初是計(jì)算切線和小圓的夾角讓Path閉合,但是計(jì)算量和難度太大了,直接使用貝塞爾曲線更省事。
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、利用反三角函數(shù)計(jì)算出小圓切線與所有小圓原點(diǎn)與(0,0)點(diǎn)的夾角
double asin = Math.asin(r / currentRadius);
//2、計(jì)算出切線長(zhǎng)度
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));
//如果使用三角函數(shù)計(jì)算切線可能很復(fù)雜,這里使用貝塞爾曲線簡(jiǎn)化邏輯
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; //控制時(shí)間,防止逃出邊界
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() {
// 實(shí)例化畫(huà)筆并打開(kāi)抗鋸齒
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、利用反三角函數(shù)計(jì)算出小圓切線與所有小圓原點(diǎn)與(0,0)點(diǎn)的夾角
double asin = Math.asin(r / currentRadius);
//2、計(jì)算出切線長(zhǎng)度
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));
//如果使用三角函數(shù)計(jì)算切線可能很復(fù)雜,這里使用貝塞爾曲線簡(jiǎn)化邏輯
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、利用反三角函數(shù)計(jì)算出小圓切線與所有小圓原點(diǎn)與(0,0)點(diǎn)的夾角
double asin = Math.asin(r / currentRadius);
//2、計(jì)算出切線長(zhǎng)度
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);
//利用反三角函數(shù)計(jì)算出切線與圓的夾角
int t = 1;
for (int i = 0; i < 2; i++) {
double aspectRadius = Math.abs(Math.cos(asin) * currentRadius); //切線長(zhǎng)度
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);
}
}
}
四、總結(jié)
本篇我們大量使用了三角函數(shù)、反三角函數(shù),因此一定要掌握好數(shù)學(xué)基礎(chǔ)。
以上就是基于Android實(shí)現(xiàn)煙花效果的詳細(xì)內(nèi)容,更多關(guān)于Android煙花效果的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Android自定義View新年煙花、祝福語(yǔ)橫幅動(dòng)畫(huà)
- Android實(shí)現(xiàn)布局動(dòng)畫(huà)和共享動(dòng)畫(huà)的結(jié)合效果
- 基于Android實(shí)現(xiàn)一個(gè)常用的布局吸頂效果
- Android?利用ImageView屬性實(shí)現(xiàn)選中和未選中效果
- Android使用AnimationDrawable實(shí)現(xiàn)閃爍紅光動(dòng)畫(huà)效果(案例詳解)
- Android進(jìn)階CoordinatorLayout協(xié)調(diào)者布局實(shí)現(xiàn)吸頂效果
相關(guān)文章
Android 多媒體播放API簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android 多媒體播放API簡(jiǎn)單實(shí)例的相關(guān)資料,這里附有代碼實(shí)例及實(shí)現(xiàn)效果圖,需要的朋友可以參考下2016-12-12
Android EditText實(shí)現(xiàn)輸入表情
editText是TextView的子類,TextView能用的工具EditText都能用,接下來(lái)通過(guò)實(shí)例代碼給大家分享Android EditText實(shí)現(xiàn)輸入表情功能,感興趣的朋友一起看看吧2017-08-08
Android-實(shí)現(xiàn)切換Fragment頁(yè)功能的實(shí)現(xiàn)代碼
本篇文章主要介紹了Android-實(shí)現(xiàn)切換Fragment頁(yè)功能的實(shí)現(xiàn)代碼,具有一定的參加價(jià)值,有興趣的可以了解一下。2017-02-02
Android通過(guò)HTTP協(xié)議實(shí)現(xiàn)斷點(diǎn)續(xù)傳下載實(shí)例
本篇文章主要介紹了Android通過(guò)HTTP協(xié)議實(shí)現(xiàn)斷點(diǎn)續(xù)傳下載實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-04-04
Android利用Service開(kāi)發(fā)簡(jiǎn)單的音樂(lè)播放功能
這篇文章主要介紹了Android利用Service開(kāi)發(fā)簡(jiǎn)單的音樂(lè)播放功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-04-04
Android Studio 配置忽略文件的方法實(shí)現(xiàn)
這篇文章主要介紹了Android Studio 配置忽略文件的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
Android 短信轉(zhuǎn)換成彩信的消息數(shù)量(實(shí)例代碼)
本文通過(guò)實(shí)例代碼給大家介紹了Android 短信轉(zhuǎn)換成彩信的消息數(shù)量,需要的朋友可以參考下2017-05-05

