开源项目ExplosionField(爆炸特效)源码分析

来源:互联网 发布:ubuntu打开u盘命令 编辑:程序博客网 时间:2024/05/16 06:40
 

开源项目ExplosionField(爆炸特效)源码分析 http://blog.csdn.net/u013022222/article/details/48995105

这是前几日在朋友圈传疯了的开源项目 如果没记错的话 小米手机卸载应用的时候就是使用的这个效果 于是我去github fork 了这个项目 地址如下:

点击打开链接



效果图:


我使用的IDE 是 android studio 

我把源码 和范例程序简单的移植到了android studio 然后随便拿了几个图(其实是QQ空间apk里的)

工程目录如下


其实很简单啦 就是个activity  点击其中每个 view 就会产生爆炸特效  首先view会颤抖下 然后爆炸

所有的源码都有注释 如果有错 欢迎指出 

下载: 点击打开链接


MainActivity.java

[java] view plaincopy
  1. package com.chan.explosionfield;  
  2.   
  3. import android.support.v7.app.AppCompatActivity;  
  4. import android.os.Bundle;  
  5. import android.view.View;  
  6. import android.view.ViewGroup;  
  7.   
  8. public class MainActivity extends AppCompatActivity {  
  9.   
  10.     //爆炸区域  
  11.     private ExplosionField mExplosionField;  
  12.   
  13.     @Override  
  14.     protected void onCreate(Bundle savedInstanceState) {  
  15.         super.onCreate(savedInstanceState);  
  16.         setContentView(R.layout.activity_main);  
  17.   
  18.         mExplosionField = ExplosionField.attach2Window(this);  
  19.         addListener(findViewById(R.id.root));  
  20.     }  
  21.   
  22.     //给需要爆炸的视图添加到爆炸区域中  
  23.     private void addListener(View root) {  
  24.   
  25.         //如果是view group 类型 就把它的子视图添加到区域中  
  26.         if (root instanceof ViewGroup) {  
  27.             ViewGroup parent = (ViewGroup) root;  
  28.             for (int i = 0; i < parent.getChildCount(); i++) {  
  29.                 addListener(parent.getChildAt(i));  
  30.             }  
  31.         }  
  32.   
  33.         //这里是View 类型的视图  
  34.         else {  
  35.   
  36.             //设置它为可点击的  
  37.             root.setClickable(true);  
  38.   
  39.             //添加监听器  
  40.             root.setOnClickListener(new View.OnClickListener() {  
  41.                 @Override  
  42.                 public void onClick(View v) {  
  43.   
  44.                     //爆炸该视图  
  45.                     mExplosionField.explode(v);  
  46.                     //取消注册其点击事件  
  47.                     v.setOnClickListener(null);  
  48.                 }  
  49.             });  
  50.         }  
  51.     }  
  52. }  

到这里不得不看explision field的源码 爆炸特效从explode那个函数开始

ExplisionField.java

[java] view plaincopy
  1. /* 
  2.  * Copyright (C) 2015 tyrantgit 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16. package com.chan.explosionfield;  
  17.   
  18. import android.animation.Animator;  
  19. import android.animation.AnimatorListenerAdapter;  
  20. import android.animation.ValueAnimator;  
  21. import android.app.Activity;  
  22. import android.content.Context;  
  23. import android.graphics.Bitmap;  
  24. import android.graphics.Canvas;  
  25. import android.graphics.Rect;  
  26. import android.util.AttributeSet;  
  27. import android.util.Log;  
  28. import android.view.View;  
  29. import android.view.ViewGroup;  
  30. import android.view.Window;  
  31.   
  32. import java.util.ArrayList;  
  33. import java.util.Arrays;  
  34. import java.util.List;  
  35. import java.util.Random;  
  36.   
  37.   
  38. /** 
  39.  * 爆炸区域 
  40.  */  
  41. public class ExplosionField extends View {  
  42.     ////////////////////////////////////////////////////////////////////////////////////////////////  
  43.     //爆炸的动画  
  44.     private List<ExplosionAnimator> mExplosions = new ArrayList<>();  
  45.     private int[] mExpandInset = new int[2];  
  46.     ////////////////////////////////////////////////////////////////////////////////////////////////  
  47.   
  48.     //ctor  
  49.     public ExplosionField(Context context) {  
  50.         super(context);  
  51.         init();  
  52.     }  
  53.   
  54.     public ExplosionField(Context context, AttributeSet attrs) {  
  55.         super(context, attrs);  
  56.         init();  
  57.     }  
  58.   
  59.     public ExplosionField(Context context, AttributeSet attrs, int defStyleAttr) {  
  60.         super(context, attrs, defStyleAttr);  
  61.         init();  
  62.     }  
  63.   
  64.     private void init() {  
  65.         Arrays.fill(mExpandInset, Utils.dp2Px(32));  
  66.     }  
  67.   
  68.     @Override  
  69.     protected void onDraw(Canvas canvas) {  
  70.         super.onDraw(canvas);  
  71.   
  72.         //这里配合Explosion Animator的draw互相调用 知道用完动画的播放时间  
  73.         for (ExplosionAnimator explosion : mExplosions) {  
  74.             explosion.draw(canvas);  
  75.         }  
  76.     }  
  77.   
  78.     public void expandExplosionBound(int dx, int dy) {  
  79.         mExpandInset[0] = dx;  
  80.         mExpandInset[1] = dy;  
  81.     }  
  82.   
  83.   
  84.     public void explode(Bitmap bitmap, Rect bound, long startDelay, long duration) {  
  85.   
  86.         //产生爆炸的动画 并且启动它  
  87.         final ExplosionAnimator explosion = new ExplosionAnimator(this, bitmap, bound);  
  88.         explosion.addListener(new AnimatorListenerAdapter() {  
  89.             @Override  
  90.             public void onAnimationEnd(Animator animation) {  
  91.                 mExplosions.remove(animation);  
  92.             }  
  93.         });  
  94.         explosion.setStartDelay(startDelay);  
  95.         explosion.setDuration(duration);  
  96.         mExplosions.add(explosion);  
  97.         explosion.start();  
  98.     }  
  99.   
  100.     /** 
  101.      * 引爆view 
  102.      * @param view 即将被引爆的view 
  103.      */  
  104.   
  105.     private int i = 0;  
  106.   
  107.     public void explode(final View view) {  
  108.   
  109.         //获得它被可见的区域  
  110.         Rect r = new Rect();  
  111.         view.getGlobalVisibleRect(r);  
  112.   
  113.         //获得当前视图在屏幕中的位置  
  114.         int[] location = new int[2];  
  115.         getLocationOnScreen(location);  
  116.   
  117.         //偏移rect 但是我没能理解这个意思  
  118.         r.offset(-location[0], -location[1]);  
  119.         r.inset(-mExpandInset[0], -mExpandInset[1]);  
  120.   
  121.         int startDelay = 100;  
  122.   
  123.         //这个动画使得view “振动”  
  124.         ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150);  
  125.         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
  126.   
  127.             Random random = new Random();  
  128.   
  129.             @Override  
  130.             public void onAnimationUpdate(ValueAnimator animation) {  
  131.                 view.setTranslationX((random.nextFloat() - 0.5f) * view.getWidth() * 0.05f);  
  132.                 view.setTranslationY((random.nextFloat() - 0.5f) * view.getHeight() * 0.05f);  
  133.             }  
  134.         });  
  135.         animator.start();  
  136.   
  137.         //让其逐渐变小 然后消失  
  138.         view.animate().setDuration(150)  
  139.                 .setStartDelay(startDelay)  
  140.                 .scaleX(0f).scaleY(0f)  
  141.                 .alpha(0f).start();  
  142.   
  143.         //爆炸相关的视图  
  144.         explode(Utils.createBitmapFromView(view),  
  145.                 r,  
  146.                 startDelay,  
  147.                 ExplosionAnimator.DEFAULT_DURATION  
  148.         );  
  149.     }  
  150.   
  151.     public void clear() {  
  152.         mExplosions.clear();  
  153.         invalidate();  
  154.     }  
  155.   
  156.     //获得爆炸区域  
  157.     public static ExplosionField attach2Window(Activity activity) {  
  158.   
  159.         //获得MainActivity layout的 根布局的父布局  
  160.         //在activity中 setContentView 会在当前布局文件外再套一个父布局  
  161.         ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
  162.         ExplosionField explosionField = new ExplosionField(activity);  
  163.   
  164.         //将爆炸区域添加到其中  
  165.         //ExplosionField extents View  
  166.         rootView.addView(explosionField, new ViewGroup.LayoutParams(  
  167.                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));  
  168.   
  169.         //返回爆炸区域  
  170.         return explosionField;  
  171.     }  
  172. }  

刚刚在MainActivity看到ExplosionField是由attach2Window这个方法产生  这里的注释是很完整的


爆炸时会产生当前view 的快照 然后根据快照 取其中的像素 作为爆炸烟火的颜色 这个均由Utils.java生成


Utils.java

[java] view plaincopy
  1. /* 
  2.  * Copyright (C) 2015 tyrantgit 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16. package com.chan.explosionfield;  
  17.   
  18.   
  19. import android.content.res.Resources;  
  20. import android.graphics.Bitmap;  
  21. import android.graphics.Canvas;  
  22. import android.graphics.drawable.BitmapDrawable;  
  23. import android.graphics.drawable.Drawable;  
  24. import android.view.View;  
  25. import android.widget.ImageView;  
  26.   
  27. public class Utils {  
  28.   
  29.     private Utils() {  
  30.     }  
  31.   
  32.     /** 
  33.      * 像素密度 
  34.      */  
  35.     private static final float DENSITY = Resources.getSystem().getDisplayMetrics().density;  
  36.     /** 
  37.      * 用来绘图 
  38.      */  
  39.     private static final Canvas sCanvas = new Canvas();  
  40.   
  41.     /** 
  42.      * 将dp 转为 像素 
  43.      * @param dp 
  44.      * @return 
  45.      */  
  46.     public static int dp2Px(int dp) {  
  47.         return Math.round(dp * DENSITY);  
  48.     }  
  49.   
  50.     /** 
  51.      * 从视图获得它的图像 
  52.      * @param view 要爆炸的view 
  53.      * @return 它的图像 
  54.      */  
  55.     public static Bitmap createBitmapFromView(View view) {  
  56.   
  57.         //如果当前的是ImageView 类型  
  58.         //那么最方便了 它的Drawable 是 BitmapDrawable的  
  59.         //可以直接获得其中的图  
  60.         if (view instanceof ImageView) {  
  61.             Drawable drawable = ((ImageView) view).getDrawable();  
  62.             if (drawable != null && drawable instanceof BitmapDrawable) {  
  63.                 return ((BitmapDrawable) drawable).getBitmap();  
  64.             }  
  65.         }  
  66.   
  67.         //如果不是  
  68.         //那么首先 就要使他失去焦点  
  69.         //因为获得了焦点的视图可能会随时就改变  
  70.         view.clearFocus();  
  71.   
  72.         //生成视图的快照 但是这个快照是空白的  
  73.         //只是当前尺寸和视图一样  
  74.         Bitmap bitmap = createBitmapSafely(view.getWidth(),  
  75.                 view.getHeight(), Bitmap.Config.ARGB_8888, 1);  
  76.   
  77.         //如果成功获得了快照  
  78.         if (bitmap != null) {  
  79.             synchronized (sCanvas) {  
  80.   
  81.                 //先设置背景为那个空白的快照  
  82.                 Canvas canvas = sCanvas;  
  83.                 canvas.setBitmap(bitmap);  
  84.   
  85.                 //将视图绘制在canvas中  
  86.                 view.draw(canvas);  
  87.   
  88.                 //然后一处空白的快照  
  89.                 //以此来获得真正的视图快照  
  90.                 canvas.setBitmap(null);  
  91.             }  
  92.         }  
  93.   
  94.         //现在空白的快照已经有了view的样子  
  95.         //是真正的快照了  
  96.         return bitmap;  
  97.     }  
  98.   
  99.     /** 
  100.      * 创建一个和指定尺寸大小一样的bitmap 
  101.      * @param width 宽 
  102.      * @param height 高 
  103.      * @param config 快照配置 详见 {@link android.graphics.Bitmap.Config} 
  104.      * @param retryCount 当生成空白bitmap发生oom时  我们会尝试再试试生成bitmap 这个为尝试的次数 
  105.      * @return 一个和指定尺寸大小一样的bitmap 
  106.      */  
  107.     public static Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int retryCount) {  
  108.         try {  
  109.             //创建空白的bitmap  
  110.             return Bitmap.createBitmap(width, height, config);  
  111.   
  112.             //如果发生了oom  
  113.         } catch (OutOfMemoryError e) {  
  114.             e.printStackTrace();  
  115.             if (retryCount > 0) {  
  116.                 //主动gc 然后再次试试  
  117.                 System.gc();  
  118.                 return createBitmapSafely(width, height, config, retryCount - 1);  
  119.             }  
  120.   
  121.             //直到次数用光  
  122.             return null;  
  123.         }  
  124.     }  
  125. }  


最后的特效都是在动画里面产生的


[java] view plaincopy
  1. /* 
  2.  * Copyright (C) 2015 tyrantgit 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16.  package com.chan.explosionfield;  
  17.   
  18. import android.animation.ValueAnimator;  
  19. import android.graphics.Bitmap;  
  20. import android.graphics.Canvas;  
  21. import android.graphics.Color;  
  22. import android.graphics.Paint;  
  23. import android.graphics.Rect;  
  24. import android.view.View;  
  25. import android.view.animation.AccelerateInterpolator;  
  26. import android.view.animation.Interpolator;  
  27.   
  28. import java.util.Random;  
  29.   
  30. public class ExplosionAnimator extends ValueAnimator {  
  31.   
  32.     /** 
  33.      * 默认的播放时间 
  34.      */  
  35.     static long DEFAULT_DURATION = 0x400;  
  36.     /** 
  37.      * 加速度补间器 
  38.      */  
  39.     private static final Interpolator DEFAULT_INTERPOLATOR = new AccelerateInterpolator(0.6f);  
  40.     private static final float END_VALUE = 1.4f;  
  41.     private static final float X = Utils.dp2Px(5);  
  42.     private static final float Y = Utils.dp2Px(20);  
  43.     private static final float V = Utils.dp2Px(2);  
  44.     private static final float W = Utils.dp2Px(1);  
  45.   
  46.     //绘制的画笔  
  47.     private Paint mPaint;  
  48.     private Particle[] mParticles;  
  49.     //要绘制的区域  
  50.     private Rect mBound;  
  51.     //要爆炸的view  
  52.     private View mContainer;  
  53.   
  54.     public ExplosionAnimator(View container, Bitmap bitmap, Rect bound) {  
  55.   
  56.         //用来画烟花  
  57.         mPaint = new Paint();  
  58.         //爆炸区域  
  59.         mBound = new Rect(bound);  
  60.   
  61.         //生成爆炸烟花点  
  62.         int partLen = 15;  
  63.         mParticles = new Particle[partLen * partLen];  
  64.   
  65.         //随机的从生成的快照里获得颜色 用作烟花点的颜色  
  66.         Random random = new Random(System.currentTimeMillis());  
  67.         int w = bitmap.getWidth() / (partLen + 2);  
  68.         int h = bitmap.getHeight() / (partLen + 2);  
  69.   
  70.         for (int i = 0; i < partLen; i++) {  
  71.             for (int j = 0; j < partLen; j++) {  
  72.   
  73.                 //要取颜色的位置  
  74.                 final int x = (j + 1) * w;  
  75.                 final int y = (i + 1) * h;  
  76.   
  77.                 //获取颜色  
  78.                 final int color = bitmap.getPixel(x, y);  
  79.                 //生成爆炸粒子  
  80.                 mParticles[(i * partLen) + j] = generateParticle(color, random);  
  81.             }  
  82.         }  
  83.   
  84.         //保存当前的视图  
  85.         mContainer = container;  
  86.   
  87.         //设置值  
  88.         setFloatValues(0f, END_VALUE);  
  89.         //设置补间器  
  90.         setInterpolator(DEFAULT_INTERPOLATOR);  
  91.         //设置动画时长  
  92.         setDuration(DEFAULT_DURATION);  
  93.     }  
  94.   
  95.     /** 
  96.      * 生成爆炸粒子 
  97.      * @param color 爆炸粒子的颜色 
  98.      * @param random 
  99.      * @return 爆炸粒子 
  100.      */  
  101.     private Particle generateParticle(int color, Random random) {  
  102.   
  103.         //生成烟花点  
  104.         Particle particle = new Particle();  
  105.         particle.color = color;  
  106.   
  107.         //设置半径  
  108.         particle.radius = V;  
  109.   
  110.         //产生随机大小的base radius  
  111.         if (random.nextFloat() < 0.2f) {  
  112.             particle.baseRadius = V + ((X - V) * random.nextFloat());  
  113.         } else {  
  114.             particle.baseRadius = W + ((V - W) * random.nextFloat());  
  115.         }  
  116.   
  117.         float nextFloat = random.nextFloat();  
  118.         particle.top = mBound.height() * ((0.18f * random.nextFloat()) + 0.2f);  
  119.         particle.top = nextFloat < 0.2f ? particle.top : particle.top + ((particle.top * 0.2f) * random.nextFloat());  
  120.         particle.bottom = (mBound.height() * (random.nextFloat() - 0.5f)) * 1.8f;  
  121.         float f = nextFloat < 0.2f ? particle.bottom : nextFloat < 0.8f ? particle.bottom * 0.6f : particle.bottom * 0.3f;  
  122.         particle.bottom = f;  
  123.         particle.mag = 4.0f * particle.top / particle.bottom;  
  124.         particle.neg = (-particle.mag) / particle.bottom;  
  125.         f = mBound.centerX() + (Y * (random.nextFloat() - 0.5f));  
  126.         particle.baseCx = f;  
  127.         particle.cx = f;  
  128.         f = mBound.centerY() + (Y * (random.nextFloat() - 0.5f));  
  129.         particle.baseCy = f;  
  130.         particle.cy = f;  
  131.         particle.life = END_VALUE / 10 * random.nextFloat();  
  132.         particle.overflow = 0.4f * random.nextFloat();  
  133.         particle.alpha = 1f;  
  134.         return particle;  
  135.     }  
  136.   
  137.     public boolean draw(Canvas canvas) {  
  138.         //直到播放完动画  
  139.         if (!isStarted()) {  
  140.             return false;  
  141.         }  
  142.   
  143.         //遍历烟花点 然后绘制  
  144.         for (Particle particle : mParticles) {  
  145.   
  146.             //设置烟花点的属性  
  147.             particle.advance((float) getAnimatedValue());  
  148.   
  149.             //如果不是透明的 那就绘制出来  
  150.             if (particle.alpha > 0f) {  
  151.                 mPaint.setColor(particle.color);  
  152.                 mPaint.setAlpha((int) (Color.alpha(particle.color) * particle.alpha));  
  153.                 canvas.drawCircle(particle.cx, particle.cy, particle.radius, mPaint);  
  154.             }  
  155.         }  
  156.   
  157.         //这里配合view 的 draw 互相调用  
  158.         mContainer.invalidate();  
  159.         return true;  
  160.     }  
  161.   
  162.     @Override  
  163.     public void start() {  
  164.         super.start();  
  165.         //这里配合view 的 draw 互相调用  
  166.         mContainer.invalidate(mBound);  
  167.     }  
  168.   
  169.     private class Particle {  
  170.         float alpha;  
  171.         int color;  
  172.         float cx;  
  173.         float cy;  
  174.         float radius;  
  175.         float baseCx;  
  176.         float baseCy;  
  177.         float baseRadius;  
  178.         float top;  
  179.         float bottom;  
  180.         float mag;  
  181.         float neg;  
  182.         float life;  
  183.         float overflow;  
  184.   
  185.         public void advance(float factor) {  
  186.             float f = 0f;  
  187.   
  188.             //这代表一个烟花点消逝的条件  
  189.             float normalization = factor / END_VALUE;  
  190.             if (normalization < life || normalization > 1f - overflow) {  
  191.                 alpha = 0f;  
  192.                 return;  
  193.             }  
  194.   
  195.             //然后计算出烟花点的半径 坐标 透明度参数  
  196.             //纯数学计算  
  197.             normalization = (normalization - life) / (1f - life - overflow);  
  198.             float f2 = normalization * END_VALUE;  
  199.             if (normalization >= 0.7f) {  
  200.                 f = (normalization - 0.7f) / 0.3f;  
  201.             }  
  202.             alpha = 1f - f;  
  203.             f = bottom * f2;  
  204.             cx = baseCx + f;  
  205.             cy = (float) (baseCy - this.neg * Math.pow(f, 2.0)) - f * mag;  
  206.             radius = V + (baseRadius - V) * f2;  
  207.         }  
  208.     }  
  209. }  
1 0