Android實(shí)現(xiàn)粒子漩渦動(dòng)畫(huà)
前言
粒子動(dòng)畫(huà)經(jīng)常用于大畫(huà)幅的渲染效果,實(shí)際上難度并不高,但是在使用粒子動(dòng)畫(huà)時(shí),必須要遵循的一些要素,主要是:
- 起點(diǎn)
- 矢量速度
- 符合運(yùn)動(dòng)學(xué)公式
起點(diǎn)之所以重要是因?yàn)槠鋵?shí)位置決定粒子出現(xiàn)的位置,矢量速度則決定了快慢和方向,運(yùn)動(dòng)學(xué)公式屬于粒子動(dòng)畫(huà)的一部分,當(dāng)然不是物理性的,畢竟平面尺寸也就那么長(zhǎng),這里的物理學(xué)公式使得畫(huà)面更加絲滑而無(wú)跳動(dòng)感覺(jué)。
本篇將實(shí)現(xiàn)下面的效果
注意:gif圖有些卡,實(shí)際上流暢很多
本篇效果實(shí)現(xiàn)
本篇效果是無(wú)數(shù)圓隨機(jī)產(chǎn)生然后漸漸變大并外旋,另外也有雨滴,這里的雨滴相對(duì)簡(jiǎn)單一些。
首先定義粒子對(duì)象
定義粒子對(duì)象是非常重要的,絕大部分傾下粒子本身就是需要單獨(dú)控制的,因?yàn)槊總€(gè)粒子的軌跡都是有所差別的。
定義圓圈粒子
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; //記錄執(zhí)行時(shí)間
生成粒子對(duì)象
- 生成雨滴是從頂部屏幕意外開(kāi)始,而y = -50f值是雨滴的高度決定。
- 圓圈是隨機(jī)產(chǎn)生,在中心位置圓圈內(nèi)。
// 創(chuàng)建新的雨滴 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); } // 創(chuàng)建新的粒子 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); }
繪制雨滴
雨滴的繪制非常簡(jiǎn)單,調(diào)用相應(yīng)的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); }
更新粒子位置
雨滴的更新相對(duì)簡(jiǎn)單,但是圓圈的旋轉(zhuǎn)是一個(gè)難點(diǎn),一個(gè)重要的問(wèn)題是如何旋轉(zhuǎn)粒子的,其實(shí)有很多方法,其中最笨的方法是旋轉(zhuǎn)Canvas坐標(biāo)系,底層有很多矩陣計(jì)算,但是這個(gè)似乎使用Math.atan2(y,x)顯然更加方便,我們只需要在當(dāng)前角度加上偏移量就能旋轉(zhuǎn)。
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); } }
粒子刷新
其實(shí)刷新機(jī)制我們以前經(jīng)常使用,調(diào)用postInvalidate即可,本身就是View自身的方法。
總結(jié)
本篇主要內(nèi)容總體上就是這些,下面是全部代碼邏輯
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; // 創(chuàng)建新的雨滴 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); } // 創(chuàng)建新的粒子 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無(wú)效從而重新繪制,實(shí)現(xiàn)動(dòng)畫(huà)效果 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實(shí)現(xiàn)粒子漩渦動(dòng)畫(huà)的詳細(xì)內(nèi)容,更多關(guān)于Android粒子漩渦的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Flutter仿微信通訊錄實(shí)現(xiàn)自定義導(dǎo)航條的示例代碼
某些頁(yè)面比如我們?cè)谶x擇聯(lián)系人或者某個(gè)城市的時(shí)候需要快速定位到我們需要的選項(xiàng),一般都會(huì)需要像微信通訊錄右邊有一個(gè)導(dǎo)航條一樣的功能,本文將利用Flutter實(shí)現(xiàn)這一效果,需要的可以參考一下2022-04-04android studio3.3.1代碼提示忽略大小寫(xiě)的設(shè)置
這篇文章主要介紹了android studio3.3.1代碼提示忽略大小寫(xiě)的設(shè)置,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03Android自定義實(shí)現(xiàn)開(kāi)關(guān)按鈕代碼
經(jīng)??梢钥吹揭恍┻x擇開(kāi)個(gè)狀態(tài)的配置文件,但是外觀都不多好看。我感覺(jué)還是自定義的比較好,下面小編給大家介紹通過(guò)Android自定義實(shí)現(xiàn)開(kāi)關(guān)按鈕代碼,感興趣的童鞋一起學(xué)習(xí)吧2016-05-05ReactiveCocoa代碼實(shí)踐之-RAC網(wǎng)絡(luò)請(qǐng)求重構(gòu)
這篇文章主要介紹了ReactiveCocoa代碼實(shí)踐之-RAC網(wǎng)絡(luò)請(qǐng)求重構(gòu) 的相關(guān)資料,需要的朋友可以參考下2016-04-04Android中View跟隨手指滑動(dòng)效果的實(shí)例代碼
這篇文章主要介紹了Android中View跟隨手指滑動(dòng)效果的實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05Flutter如何通過(guò)一行命令解決多個(gè)pubspec.yaml文件的依賴項(xiàng)問(wèn)題
這篇文章主要介紹了Flutter如何通過(guò)一行命令解決多個(gè)pubspec.yaml文件的依賴項(xiàng)問(wèn)題,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06Android底部導(dǎo)航組件BottomNavigationView
這篇文章主要介紹了Android底部導(dǎo)航組件BottomNavigationView,BottomNavigationView是相當(dāng)于一個(gè)導(dǎo)航的標(biāo)簽,但是它的形式就是像QQ微信之類的界面,至于寫(xiě)出后怎樣綁定這三個(gè)界面,就得用Fragment,寫(xiě)這三個(gè)頁(yè)面的布局2023-03-03Android itemDecoration接口實(shí)現(xiàn)吸頂懸浮標(biāo)題
這篇文章主要介紹了Android中使用itemdecoration實(shí)現(xiàn)吸頂懸浮標(biāo)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-11-11Android數(shù)字華容道小游戲開(kāi)發(fā)
這篇文章主要為大家詳細(xì)介紹了Android數(shù)字華容道小游戲開(kāi)發(fā)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01Android仿百度谷歌搜索自動(dòng)提示框AutoCompleteTextView簡(jiǎn)單應(yīng)用示例
這篇文章主要介紹了Android仿百度谷歌搜索自動(dòng)提示框AutoCompleteTextView簡(jiǎn)單應(yīng)用,結(jié)合實(shí)例形式分析了AutoCompleteTextView Widget使用步驟與相關(guān)操作技巧,需要的朋友可以參考下2016-10-10