基于Android實現(xiàn)煙花效果
一、效果預覽
無中心版本
有中心版本
二、實現(xiàn)
2.1 均勻分布
我們首要解決的問題是計算出粒子運動方向,保證粒子能正常擴散到目標范圍和區(qū)域,另外還有保證粒子盡可能隨機和均勻分布在任意方向。
方法是:
粒子擴散的范圍是一個圓的范圍內(nèi),我們要盡可能利用圓的旋轉(zhuǎn)半徑和夾角之間的關系,屬于高中數(shù)學知識。另外也要控制粒子的數(shù)量,防止堆疊過多的問題。
int t = i % 12; double degree = random.nextFloat() * 30 + t * 30; // 12等分圓,沒等分都保證產(chǎn)生粒子 // 360 /12 = 30 ,意味著每等分30度區(qū)域內(nèi)需要產(chǎn)生一定的粒子
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); //利用反三角函數(shù)計算出切線與圓的夾角 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 進一步優(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ù)計算出小圓切線與所有小圓原點與(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動畫并沒有和粒子圓點閉合,這樣就會有問題,后續(xù)如果要使用Shader著色 (為啥要用Shader著色,主要是火焰效果很難畫出來,還得借助一些其他工具),必然產(chǎn)生不均勻問題。為了實現(xiàn)開頭的效果,最初是計算切線和小圓的夾角讓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、利用反三角函數(shù)計算出小圓切線與所有小圓原點與(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)); //如果使用三角函數(shù)計算切線可能很復雜,這里使用貝塞爾曲線簡化邏輯 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、利用反三角函數(shù)計算出小圓切線與所有小圓原點與(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)); //如果使用三角函數(shù)計算切線可能很復雜,這里使用貝塞爾曲線簡化邏輯 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ù)計算出小圓切線與所有小圓原點與(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); //利用反三角函數(shù)計算出切線與圓的夾角 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); } } }
四、總結
本篇我們大量使用了三角函數(shù)、反三角函數(shù),因此一定要掌握好數(shù)學基礎。
以上就是基于Android實現(xiàn)煙花效果的詳細內(nèi)容,更多關于Android煙花效果的資料請關注腳本之家其它相關文章!
相關文章
Android-實現(xiàn)切換Fragment頁功能的實現(xiàn)代碼
本篇文章主要介紹了Android-實現(xiàn)切換Fragment頁功能的實現(xiàn)代碼,具有一定的參加價值,有興趣的可以了解一下。2017-02-02Android通過HTTP協(xié)議實現(xiàn)斷點續(xù)傳下載實例
本篇文章主要介紹了Android通過HTTP協(xié)議實現(xiàn)斷點續(xù)傳下載實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04Android利用Service開發(fā)簡單的音樂播放功能
這篇文章主要介紹了Android利用Service開發(fā)簡單的音樂播放功能,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2023-04-04Android Studio 配置忽略文件的方法實現(xiàn)
這篇文章主要介紹了Android Studio 配置忽略文件的方法實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-10-10Android 短信轉(zhuǎn)換成彩信的消息數(shù)量(實例代碼)
本文通過實例代碼給大家介紹了Android 短信轉(zhuǎn)換成彩信的消息數(shù)量,需要的朋友可以參考下2017-05-05