Android實現(xiàn)粒子中心擴散動畫效果
前言
粒子動畫效果相比其他動畫來說是非常復雜了的,主要涉及三個方面,粒子初始化、粒子位移、粒子回收等問題,其中特別是粒子位移是最復雜的,涉及到的數(shù)學邏輯非常多,主要是各種三角函數(shù)、物理學公式等。
本篇將實現(xiàn)兩種動畫效果,代碼基本相同,只是旋轉速度不一樣,因此,本篇其實可以看作一篇模板文章,具體效果可以通過調節(jié)參數(shù)生成各種動畫
第一種動畫
第二種動畫
實現(xiàn)步驟
其實和以往的粒子效果一樣,粒子需要被管理起來,因此我們需要有容器、也需要粒子對象
粒子對象定義
下面是創(chuàng)建粒子對象的邏輯,基本屬性在注釋中了
static class Circle { int maxLength; //最大運行距離 float speed; //外擴速度 float rotate; // 角速度 private float degree; //起始角度 private int y; //y坐標 private int x; //x坐標 private int color; //顏色 private float radius; //小圓半徑 private float drawRadius; //繪制時的小圓半徑 public Circle(int color, int maxLength, float radius, float degree) { this.color = color; this.radius = radius; this.maxLength = maxLength; this.degree = degree; this.x = (int) (radius * Math.cos(degree)); this.y = (int) (radius * Math.sin(degree)); this.rotate = 0.35f; //觸角效果 this.speed = 0.2f; } }
粒子更新
在任何動畫中,粒子運動必須具備時間屬性,任何符合物理學的位移運動,速度和時間的關系是位移計算的方法。下面,我們繼續(xù)給Circle類添加更新方法。
這里一個重要的知識點是
- Math.hypot(x, y) :平方根計算
- Math.atan2(y, x): 斜率計算,注意,此角度具備方向
public boolean update(long timeline) { float length = (float) Math.hypot(x, y); //計算當前移動的距離(距離中心點) float center = length + this.speed * timeline; //計算即將到達的距離 float ratio = center / maxLength; //計算與最遠距離的比值 this.drawRadius = (1f - ratio) * radius; //距離越遠,圓的半徑越小 double degree = Math.atan2(y, x) + rotate; //即將旋轉的角度 this.x = (int) (center * Math.cos(degree)); //新的x this.y = (int) (center * Math.sin(degree)); //新的y if (drawRadius <= 0) { return false; //如果半徑為0時,意味著圓看不見了,因此要坐下標記 } return true; }
粒子繪制方法
繪制自身其實很簡單,只需要簡單的調用Canvas相關邏輯即可
public void draw(Canvas canvas, TextPaint paint) { paint.setColor(color); canvas.drawCircle(x, y, drawRadius, paint); }
粒子回收
為了減少內存申請頻率,我們對跑出邊界的粒子進行重置
public void reset() { this.x = (int) (radius * Math.cos(degree)); this.y = (int) (radius * Math.sin(degree)); }
View邏輯
以上是完整的粒子對象邏輯,接下來我們實現(xiàn)一個View,用來管理和繪制粒子。
int maxCircleRadius = 20; //粒子初始半徑 List<Circle> circleList = new ArrayList<>(); //容器 int maxCircleNum = 300; //最大數(shù)量
繪制邏輯
首先是初始化,我們這里設置了3種粒子,因此間隔角度是120度,而我們每次增加三種,防止出現(xiàn)混亂的問題。
final float rotateDegree = (float) Math.toRadians(120f); //間隔角度 if (circleList.size() < maxCircleNum) { //每次增加三種 circleList.add(new Circle(Color.RED, (int) maxRadius, maxCircleRadius, 0 * rotateDegree)); circleList.add(new Circle(Color.GREEN, (int) maxRadius, maxCircleRadius, 1 * rotateDegree)); circleList.add(new Circle(Color.CYAN, (int) maxRadius, maxCircleRadius, 2 * rotateDegree)); }
下面是每個粒子的繪制邏輯
for (int i = 0; i < circleList.size(); i++) { Circle circle = circleList.get(i); circle.draw(canvas, mPaint); //繪制方法 }
更新粒子
下面有個重要的邏輯,其實前面也提到過,就是重置跑出邊界的粒子
for (int i = 0; i < circleList.size(); i++) { Circle circle = circleList.get(i); if(!circle.update(16)){ circle.reset(); //如果不能更新,則進行重置 } } postInvalidate(); //刷新繪制邏輯
以上就是整體核心邏輯
效果調節(jié)
我們開頭的兩種效果其實是同一個View實現(xiàn)的,這其中一個重要的點就是速度調整,文章開頭是調整出的兩種效果,當然染還可以調整出其他效果 第一種
this.rotate = 0.2f; this.speed = 0.2f; //外擴效果
第二種
this.rotate = 0.35f; //觸角效果 this.speed = 0.2f;
第三種
this.rotate = 0.8f; this.speed = 0.1f;
當然,還有更多,篇幅原因就不深入了。
總結
本篇到這里就結束了,其實我們的核心代碼并不多,但是簡單的邏輯就能衍生出很多動畫效果。其實,學習粒子動畫是非常有意思的事,很多時候,你在實現(xiàn)某些效果的途中,就能突然開發(fā)出一種新的動畫效果。
本篇代碼
下面是本篇內容的完整邏輯,基本就在100行左右。
public class CircleParticleView extends View { private TextPaint mPaint; private DisplayMetrics mDM; public CircleParticleView(Context context) { this(context, null); } public CircleParticleView(Context context, AttributeSet attrs) { super(context, attrs); mDM = getResources().getDisplayMetrics(); initPaint(); } private void initPaint() { //否則提供給外部紋理繪制 mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); mPaint.setStrokeCap(Paint.Cap.ROUND); PaintCompat.setBlendMode(mPaint, BlendModeCompat.PLUS); } @Override protected void onMeasure(int widthMeasureSpec, int 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; } setMeasuredDimension(widthSize, heightSize); } int maxCircleRadius = 20; List<Circle> circleList = new ArrayList<>(); int maxCircleNum = 300; long time = 0; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); float maxRadius = Math.min(width, height) / 2f; int save = canvas.save(); canvas.translate(width / 2f, height / 2f); final float rotateDegree = (float) Math.toRadians(120f); if (circleList.size() < maxCircleNum) { circleList.add(new Circle(Color.RED, (int) maxRadius, maxCircleRadius, 0 * rotateDegree)); circleList.add(new Circle(Color.GREEN, (int) maxRadius, maxCircleRadius, 1 * rotateDegree)); circleList.add(new Circle(Color.CYAN, (int) maxRadius, maxCircleRadius, 2 * rotateDegree)); } mPaint.setStyle(Paint.Style.FILL); for (int i = 0; i < circleList.size(); i++) { Circle circle = circleList.get(i); circle.draw(canvas, mPaint); } canvas.restoreToCount(save); for (int i = 0; i < circleList.size(); i++) { Circle circle = circleList.get(i); if (!circle.update(16)) { circle.reset(); } } postInvalidate(); time += 16; } static class Circle { int maxLength; //最大運行距離 float speed; //外擴速度 float rotate; // 角速度 private float degree; //起始角度 private int y; //y坐標 private int x; //x坐標 private int color; //顏色 private float radius; //小圓半徑 private float drawRadius; //繪制時的小圓半徑 public Circle(int color, int maxLength, float radius, float degree) { this.color = color; this.radius = radius; this.maxLength = maxLength; this.degree = degree; this.x = (int) (radius * Math.cos(degree)); this.y = (int) (radius * Math.sin(degree)); this.rotate = 0.35f; //觸角效果 this.speed = 0.2f; } public boolean update(long timeline) { float length = (float) Math.hypot(x, y); float center = length + this.speed * timeline; //距離增加 float ratio = center / maxLength; this.drawRadius = (1f - ratio) * radius; double degree = Math.atan2(y, x) + rotate; //角度增加 this.x = (int) (center * Math.cos(degree)); this.y = (int) (center * Math.sin(degree)); if (drawRadius <= 0) { return false; } return true; } public void draw(Canvas canvas, TextPaint paint) { paint.setColor(color); canvas.drawCircle(x, y, drawRadius, paint); } public void reset() { this.x = (int) (radius * Math.cos(degree)); this.y = (int) (radius * Math.sin(degree)); } } }
以上就是Android實現(xiàn)粒子中心擴散動畫效果的詳細內容,更多關于Android粒子中心擴散的資料請關注腳本之家其它相關文章!
相關文章
Android RecyclerView item選中放大被遮擋問題詳解
這篇文章主要介紹了Android RecyclerView item選中放大被遮擋問題詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04Android ViewPager實現(xiàn)選項卡切換
這篇文章主要介紹了Android ViewPager實現(xiàn)選項卡切換,詳細分析了ViewPager實現(xiàn)選項卡切換功能,感興趣的小伙伴們可以參考一下2016-02-02Android實現(xiàn)從底部彈出的Dialog的實例代碼
這篇文章主要介紹了Android實現(xiàn)從底部彈出的Dialog的實例代碼,非常不錯,具有參考借鑒價值 ,需要的朋友可以參考下2018-04-04詳解Android 利用Iptables實現(xiàn)網(wǎng)絡黑白名單(防火墻)
這篇文章主要介紹了詳解Android 利用Iptables實現(xiàn)網(wǎng)絡黑白名單(防火墻),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08