转载

【Android效果集】学习ExplosionField之粒子破碎效果

前段时间在某效果网站看到开源项目【ExplosionField】非常喜欢,于是自己跟着源码学习着去做了做。跟源码效果有一点区别,我都是尽力读懂源码然后用自己的理解写出来,源码有些看不懂的地方,我也就没有用到,因为自己的代码要保证自己都能看懂。

最后效果如下:

【Android效果集】学习ExplosionField之粒子破碎效果

(本文适合有一年Android开发经验者学习)

本文可以学到:

1.开源项目ExplosionField的实现思路 

2.图示效果的实现过程 

3.属性动画的用法

实现思路:

1.新建一个 Bean Particle,表示一个粒子对象;新建一个 View ExplosionField作为画布用来显示破碎的粒子;新建一个属性动画(ValueAnimator) ExplosionAnimator用来改变不同时刻的粒子状态;

2.通过View生成图片Bitmap,把生成的图片分解成若干个粒子,让每个粒子记录特定的位置,所有的粒子组合能看出是原图。

3.加上动画效果,使得点击View后,粒子能有所变化。

4.构思算法,形成不一样的效果。

5.匹配不同分辨率的设备。

6.重构。

详细过程:

可以先看看项目结构,非常简单:

【Android效果集】学习ExplosionField之粒子破碎效果 v                                    v           

1.新建对象

1.1 新建Particle对象,用来描述粒子,包括属性有颜色、透明度、圆心坐标、半径。

public class Particle {     float cx; //center x of circle     float cy; //center y of circle     float radius;      int color;     float alpha; }

1.2 新建ExplosionField对象,继承自View,用于做粒子集的画布,需要重写onDraw()方法

public class ExplosionField extends View{      public ExplosionField(Context context) {         super(context);         init();     }      public ExplosionField(Context context, AttributeSet attrs) {         super(context, attrs);         init();     }     private void init() {        //初始化     }      @Override     protected void onDraw(Canvas canvas) {         super.onDraw(canvas);         //绘制粒子     } }

1.3 新建ExplosionAnimator,继承自ValueAnimator,用来执行自定义动画。ValueAnimator简单来说就是在一段时间内通过不断改变值(一般是改变某个属性的值)来达到动画效果。更多可以参考 《Android属性动画完全解析(上),初识属性动画的基本用法》 来学习。

而我们现在是准备在一段时间内(大概1.5秒)让ValueAnimator里的值从0.0f变化到1.0f,然后根据系统生成的递增随机值(范围在0.0f~1.0f)改变Particle里的属性值。

public class ExplosionAnimator extends ValueAnimator{     public static final int DEFAULT_DURATION = 1500;      public ExplosionAnimator() {         setFloatValues(0.0f, 1.0f);         setDuration(DEFAULT_DURATION);     } }

这样,在1.5秒内,通过ExplosionAnimator的方法getAnimatedValue()就能够不断得到递增的范围在0.0f~1.0f之间的值。

2.复制出View的快照图片

首先通过view的宽高创建出一个同样大小的空白图,用Bitmap的静态方法createBitmap()创建,最后一个参数表示图片质量。

 Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);

然后通过画布Canvas,先把空白图设置到画布里,再让view把自己画在画布上,空白图也变成了view的翻版了。

   mCanvas.setBitmap(bitmap);     view.draw(mCanvas);     //此处bitmap已是同view显示一样的图

完整代码:

//ExplosionField.java  public class ExplosionField extends View{         private static final Canvas mCanvas = new Canvas();          private Bitmap createBitmapFromView(View view) {         Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);          if (bitmap != null) {             synchronized (mCanvas) {                 mCanvas.setBitmap(bitmap);                 view.draw(mCanvas);                 mCanvas.setBitmap(null); //清除引用             }         }         return bitmap;     } }

PS:在原项目ExplosionField中还有一个判断,如果view是ImageView的对象,那么直接获得ImageView依附的BitmapDrawable图。

     if (view instanceof ImageView) {             Drawable drawable = ((ImageView)view).getDrawable();             if (drawable != null && drawable instanceof BitmapDrawable) {                 return ((BitmapDrawable) drawable).getBitmap();             }     }

我为什么去掉了呢?是因为如果ImageView设置了背景(background)的话,这样直接获取的BitmapDrawable是src的引用,并不包括背景色。所以统一用画布绘制的方法生成快照。

好了,先拿一个TextView做示范,看看复制的效果:

【Android效果集】学习ExplosionField之粒子破碎效果

3.把快照分解成若干粒子

前面我们已经生成了快照图片,现在我们需要把快照分解成若干个粒子,这些粒子的组合能看出来是原图的影子,然后再让粒子动起来形成后面的动画。

那怎么做呢?ExplosionField项目是分解成15 * 15个粒子,我这里有点不一样我就直接按照我的思路讲解了。

首先定义一个二维数组Particle[][](一维的也行啦,原项目就是定义一维的),用来存放所有粒子,因为图片大小不同,粒子个数也不会相同,所以我们把粒子的宽高固定,在Particle类中新加一个静态常量属性

   public static final int PART_WH = 8; //默认小球宽高

然后根据view的宽高,算出横竖粒子的个数

//ExplosionAnimator.java - generateParticles(Bitmap bitmap, Rect bound)          int w = bound.width();         int h = bound.height();          int partW_Count = w / Particle.PART_WH; //横向个数         int partH_Count = h / Particle.PART_WH; //竖向个数          Particle[][] particles = new Particle[partH_Count][partW_Count];

其中bound是Rect类型,通过view.getGlobalVisibleRect()方法能得到view相对于整个屏幕的坐标

    Rect bound = new Rect();     view.getGlobalVisibleRect(rect);

然后把二维粒子数组对应图片的位置,设置为相应的颜色属性和坐标。

通过bitmap.getPixel(x, y)可以获得(x, y)坐标的bitmap的颜色值

//ExplosionAnimator.java - generateParticles(Bitmap bitmap, Rect bound)          Point point = null;         for (int row = 0; row < partH_Count; row ++) { //行             for (int column = 0; column < partW_Count; column ++) { //列                 //取得当前粒子所在位置的颜色                 int color = bitmap.getPixel(column * partW_Count, row * partH_Count);                  point = new Point(column, row); //x是列,y是行                  particles[row][column] = Particle.generateParticle(color, bound, point);             }         }

在Particle类中定义静态方法generateParticle()用来生成新的Particle对象

//Particle.java      public static Particle generateParticle(int color, Rect bound, Point point) {         int row = point.y; //行是高         int column = point.x; //列是宽          Particle particle = new Particle();         particle.mBound = bound;         particle.color = color;         particle.alpha = 1f;          particle.radius = PART_WH;         particle.cx = bound.left + PART_WH * column;         particle.cy = bound.top + PART_WH * row;          return particle;     }

这里把半径设置为宽长,而不是宽的一半,是因为叠加显示效果会更好看一点。

为了能够显示出来,我们新建一个draw()方法,用从ExplosionField传来的canvas来绘制所有粒子

//ExplosionAnimator.java      public void draw(Canvas canvas) {         for (Particle[] particle : mParticles) {             for (Particle p : particle) {                 canvas.drawCircle(p.cx, p.cy, p.radius, mPaint);             }         }     }   //ExplosionField.java      private ArrayList<ExplosionAnimator> explosionAnimators;      @Override     protected void onDraw(Canvas canvas) {         super.onDraw(canvas);         for (ExplosionAnimator animator : explosionAnimators) {             animator.draw(canvas);         }     }

因为画布可能同时绘制几个动画,所以用一个List保存动画集。

现在大概的效果是这样:

【Android效果集】学习ExplosionField之粒子破碎效果 【Android效果集】学习ExplosionField之粒子破碎效果

4.加上动画,使得粒子动起来

前面说过,在ExplosionAnimator中通过方法getAnimatedValue()就能够不断得到递增的范围在0.0f~1.0f之间的值(记做factor)。

我们先在Particle写好得到变化因素后,属性要发生的改变。cx左右移动都可以,cy向下移动且距离和view高度有关(不同高度图片,每次下降距离不同),radius变小,alpha变得越来越透明。只要符合这几点,算法随便写就可以了。

//Particle.java      public void advance(float factor) {         cx = cx + factor * random.nextInt(mBound.width()) * (random.nextFloat() - 0.5f);         cy = cy + factor * random.nextInt(mBound.height() / 2);          radius = radius - factor * random.nextInt(2);          alpha = (1f - factor) * (1 + random.nextFloat());     }

记住传进来的factor是从0.0f到1.0f不断递增的。

然后改造draw()方法,每次绘制都让粒子“前进一步”调用一次advance()方法,然后根据新属性重新绘制

//ExplosionAnimator.java      public void draw(Canvas canvas) {         if(!isStarted()) { //动画结束时停止             return;         }         for (Particle[] particle : mParticles) {             for (Particle p : particle) {                  p.advance((Float) getAnimatedValue());                  mPaint.setColor(p.color);                 mPaint.setAlpha((int) (Color.alpha(p.color) * p.alpha)); //这样透明颜色就不是黑色了                  canvas.drawCircle(p.cx, p.cy, p.radius, mPaint);             }         }          mContainer.invalidate();     }

最后一句的mContainer其实就是ExplosionField,调用它的invalidate()方法,就是调用ExplosionField的onDraw()方法。而ExplosionField的onDraw()里又调用了ExplosionAnimator的draw()方法。这样循环就出现了动画效果。

结束的条件就是第一句if(!isStarted())如果动画停止了,就断了绘制循环。

PS:这里值得一提的有setAlpha()方法,之前我用的是

    mPaint.setColor(p.color);     mPaint.setAlpha((int) (255 * p.alpha));

这样有个问题就是当颜色为透明时,显示的是黑色。

而改为了方法:

   mPaint.setColor(p.color);    mPaint.setAlpha((int) (Color.alpha(p.color) * p.alpha));

透明颜色就为透明色了。

现在动画过程已经写完,就差开始的导火线了,我们在动画开始的时候启动这根导火线,重写start()方法:

//ExplosionAnimator.java      @Override     public void start() {         super.start();         mContainer.invalidate();     }

那在哪使动画开始呢,即在哪调用explosionAnimator.start()呢?

在ExplosionField中建立一个“爆炸”方法,只要调用这个方法,传入view,最后执行animator.start(),view就会执行爆炸效果

    public void explode(final View view) {         Rect rect = new Rect();         view.getGlobalVisibleRect(rect); //得到view相对于整个屏幕的坐标         rect.offset(0, -Utils.dp2px(25)); //去掉状态栏高度          final ExplosionAnimator animator = new ExplosionAnimator(this, createBitmapFromView(view), rect);         explosionAnimators.add(animator);          animator.addListener(new AnimatorListenerAdapter() {             @Override             public void onAnimationStart(Animator animation) {                 view.animate().alpha(0f).setDuration(150).start();             }              @Override             public void onAnimationEnd(Animator animation) {                 view.animate().alpha(1f).setDuration(150).start();                  //动画结束时从动画集中移除                 explosionAnimators.remove(animation);                 animation = null;             }         });         animator.start();     }

现在的效果:

【Android效果集】学习ExplosionField之粒子破碎效果

5.附着动画到任意Activity,添加监听器给需要有动画效果的view

现在动画效果什么的都做好了,要如何使用呢?

现在的思路是在Activity的最上层盖一层透明的ExplosionField视图,用来显示粒子动画。

//ExplosionField.java     /**      * 给Activity加上全屏覆盖的ExplosionField      */     private void attach2Activity(Activity activity) {         ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);          ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);         rootView.addView(this, lp);     }

其实Activity的根视图并不是我们设置的xml,它上面还有一层,通过findViewById(Window.ID_ANDROID_CONTENT)能够得到,然后我们再把ExplosionField全屏加载在Activity的最上层,这样显示动画效果就不会被遮盖。

然后我们可以在初始化的时候加上这个方法:

public class ExplosionField extends View{      public ExplosionField(Context context) {         super(context);         init();     }      public ExplosionField(Context context, AttributeSet attrs) {         super(context, attrs);         init();     }     private void init() {         ...         attach2Activity((Activity) getContext());     }      ... }

在看Activity的onCreate()方法就非常简单了:

//MainActivity.java      protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main_az);          ExplosionField explosionField = new ExplosionField(this);          explosionField.addListener(findViewById(R.id.root));     }

最后一句调用了addListener()方法,就是把需要实现点击破碎效果的view加上监听器,看代码:

    public void addListener(View view) {         if (view instanceof ViewGroup) {             ViewGroup viewGroup = (ViewGroup) view;             int count = viewGroup.getChildCount();             for (int i = 0 ; i < count; i++) {                 addListener(viewGroup.getChildAt(i));             }         } else {             view.setClickable(true);             view.setOnClickListener(getOnClickListener());         }     }       private OnClickListener getOnClickListener() {         if (null == onClickListener) {              onClickListener = new View.OnClickListener() {                 @Override                 public void onClick(View v) {                     ExplosionField.this.explode(v);                 }             };         }          return onClickListener;     }

只要传入ViewGroup,会自动递归查找Child View,并给Child View加上点击监听器,一旦点击就调用爆破方法执行动画。

最终效果大图:

【Android效果集】学习ExplosionField之粒子破碎效果

更多详细代码可 fork 源码查看!

源码地址: https://github.com/Xieyupeng520/AZExplosion

如果你喜欢这个效果,请给我Github上一个Star鼓励一下哈O(∩_∩)O谢谢!

原文出处: http://blog.csdn.net/XieYupeng520/article/details/49951835  

正文到此结束
Loading...