简介

最近在闲逛的时候,发现了一款粒子爆炸特效的控件,觉得比较有意思,效果也不错。 
但是代码不好扩展,也就是说如果要提供不同的爆炸效果,需要修改的地方比较多。于是我对源代码进行了一些重构,将爆炸流程和粒子运动分离。 
对于源码,大家可以参考以下链接 
链接1 
链接2

上面两套代码,其实结构都是一样的,但是实现的效果不同(其实就是粒子运动的算法不同)。 
本篇文章,将给大家介绍粒子爆炸特效的实现方式,替大家理清实现思路。

实现效果如下: 

类设计

类设计图如下: 

ExplosionField爆炸效果发生的场地,是一个View。当一个控件需要爆炸时,需要为控件生成一个ExplosionField,这个ExplosionField**覆盖整个屏幕**,于是我们才能看到完整的爆炸效果。

ExplosionAnimator,爆炸动画,其实是一个计时器,继承自ValueAnimator。1024s内,完成爆炸动画,每次计时,就更新所有粒子的运动状态。draw()方法是它最重要的方法,也就是使所有粒子重绘自身,从而实现动画效果。

ParticleFactory,是一个抽象类。用于产生粒子数组,不同的ParticleFactory可以产生不同类型的粒子数组。

Particle,抽象的粒子类。代表粒子本身,必须拥有的属性包括,当前自己的cx,cy坐标和颜色color。必须实现两个方法,d**raw()方法选择怎么绘制自身(圆形还是方形等),**caculate()计算当前时间,自己所处的位置

控件的使用

控件使用很简单,首先要实现不同的爆炸效果,需要给ExplosionField传入不同的ParticleFactory工厂,产生不同的粒子。

ExplosionField explosionField = new ExplosionField(this,new FallingParticleFactory());
  • 1

然后哪个控件需要爆炸效果,就这样添加

explosionField.addListener(findViewById(R.id.text));
explosionField.addListener(findViewById(R.id.layout1));
  • 1
  • 2

这样就为两个控件添加了爆炸效果,注意layout1代表的是一个viewgroup,那么我们就会为viewgroup中的每个view添加爆炸效果。 
我们可以想象,在ExplosionField的构造函数中,传入不同的ParticleFactory,就可以生成不同的爆炸效果。

爆炸实现思路

1、获取当前控件背景bitmap

例如,例子中使用的是imageview,对于这个控件,我提供了一个工具类,可以获得其背景的Bitmap对象

public static Bitmap createBitmapFromView(View view) {
view.clearFocus();
Bitmap bitmap = createBitmapSafely(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888, 1);
if (bitmap != null) {
synchronized (sCanvas) {
Canvas canvas = sCanvas;
canvas.setBitmap(bitmap);
view.draw(canvas);
canvas.setBitmap(null);
}
}
return bitmap;
} public static Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int retryCount) {
try {
return Bitmap.createBitmap(width, height, config);
} catch (OutOfMemoryError e) {
e.printStackTrace();
if (retryCount > 0) {
System.gc();
return createBitmapSafely(width, height, config, retryCount - 1);
}
return null;
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

上面的方法,简而言之,就是将控件的Bitmap对象复制了一份,然后返回。 
我们知道,bitmap可以看成是一个像素矩阵,矩阵上面的点,就是一个个带有颜色的像素,于是我们可以获取每个点(未必需要每个)的颜色和位置,组装成一个对象Particle,这么一来,Particle就代表带有颜色的点了。

2、将背景bitmap转换成Particle数组

获取Bitmap以后,我们交给ParticleFactory进行加工,根据Bitmap生产Particle数组。

public abstract class ParticleFactory {
public abstract Particle[][] generateParticles(Bitmap bitmap, Rect bound);
}
  • 1
  • 2
  • 3

例如我们来看一个简单实现类,也是gif图中,第一个下落效果的工厂类

public class FallingParticleFactory extends ParticleFactory{
public static final int PART_WH = 8; //默认小球宽高 public Particle[][] generateParticles(Bitmap bitmap, Rect bound) {
int w = bound.width();//场景宽度
int h = bound.height();//场景高度 int partW_Count = w / PART_WH; //横向个数
int partH_Count = h / PART_WH; //竖向个数 int bitmap_part_w = bitmap.getWidth() / partW_Count;
int bitmap_part_h = bitmap.getHeight() / partH_Count; Particle[][] particles = new Particle[partH_Count][partW_Count];
Point point = null;
for (int row = 0; row < partH_Count; row ++) { //行
for (int column = 0; column < partW_Count; column ++) { //列
//取得当前粒子所在位置的颜色
int color = bitmap.getPixel(column * bitmap_part_w, row * bitmap_part_h); float x = bound.left + FallingParticleFactory.PART_WH * column;
float y = bound.top + FallingParticleFactory.PART_WH * row;
particles[row][column] = new FallingParticle(color,x,y,bound);
}
} return particles;
} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

其中Rect类型的bound,是代表原来View控件的宽高信息。 
根据我们设定的每个粒子的大小,和控件的宽高,我们就可以计算出,有多少个粒子组成这个控件的背景。 
我们取得每个粒子所在位置的颜色,位置,用于生产粒子,这就是FallingParticle。

3、生成爆炸场地,开始爆炸动画流程

爆炸时需要场地的,也就是绘制粒子的地方,我们通过给当前屏幕,添加一个覆盖全屏幕的ExplosionField来作为爆炸场地。

public class ExplosionField extends View{
... /**
* 给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);
}
...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

爆炸场地添加以后,我们响应控件的点击事件,开始动画 
首先是震动动画

 /**
* 爆破
* @param view 使得该view爆破
*/
public void explode(final View view) {
//防止重复点击
if(explosionAnimatorsMap.get(view)!=null&&explosionAnimatorsMap.get(view).isStarted()){
return;
}
if(view.getVisibility()!=View.VISIBLE||view.getAlpha()==0){
return;
}
//为了正确绘制粒子
final Rect rect = new Rect();
view.getGlobalVisibleRect(rect); //得到view相对于整个屏幕的坐标
int contentTop = ((ViewGroup)getParent()).getTop();
Rect frame = new Rect();
((Activity) getContext()).getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
rect.offset(0, -contentTop - statusBarHeight);//去掉状态栏高度和标题栏高度 //震动动画
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { Random random = new Random(); @Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setTranslationX((random.nextFloat() - 0.5f) * view.getWidth() * 0.05f);
view.setTranslationY((random.nextFloat() - 0.5f) * view.getHeight() * 0.05f);
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
explode(view, rect);//爆炸动画
}
});
animator.start();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

震动动画很简单,就是x,y方向上,随机产生一些位移,使原控件发生移动即可。 
在震动动画的最后,调用了爆炸动画,于是爆炸动画开始。

private void explode(final View view,Rect rect) {
final ExplosionAnimator animator = new ExplosionAnimator(this, Utils.createBitmapFromView(view), rect,mParticleFactory);
explosionAnimators.add(animator);
explosionAnimatorsMap.put(view, animator);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
//缩小,透明动画
view.animate().setDuration(150).scaleX(0f).scaleY(0f).alpha(0f).start();
} @Override
public void onAnimationEnd(Animator animation) {
view.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(150).start(); //动画结束时从动画集中移除
explosionAnimators.remove(animation);
explosionAnimatorsMap.remove(view);
animation = null;
}
});
animator.start();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

爆炸动画首先将原控件隐藏。 
我们来看爆炸动画的具体实现

public class ExplosionAnimator extends ValueAnimator {
... public ExplosionAnimator(View view, Bitmap bitmap, Rect bound,ParticleFactory particleFactory) {
mParticleFactory = particleFactory;
mPaint = new Paint();
mContainer = view;
setFloatValues(0.0f, 1.0f);
setDuration(DEFAULT_DURATION);
mParticles = mParticleFactory.generateParticles(bitmap, bound);
}
//最重要的方法
public void draw(Canvas canvas) {
if(!isStarted()) { //动画结束时停止
return;
}
//所有粒子运动
for (Particle[] particle : mParticles) {
for (Particle p : particle) {
p.advance(canvas,mPaint,(Float) getAnimatedValue());
}
}
mContainer.invalidate();
} @Override
public void start() {
super.start();
mContainer.invalidate();
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

实现很简单,就是根据工厂类,生成粒子数组。 
而其实质是一个ValueAnimator,在一定时间内,从0数到1。 
然后提供了一个draw()方法,方法里面调用了每个粒子的advance()方法,并且传入了当前数到的数字(是一个小数)。 
advance()方法里,其实调用了draw()方法和caculate()方法。

上面的实现,其实是一个固定的流程,添加了爆炸场地以后,我们就开始从0数到1,在这个过程中,粒子会根据当前时间,绘制自己的位置,所以粒子的位置,其实是它自己决定的,和流程无关。 
也就是说,我们只要用不同的算法,绘制粒子的位置即可,实现了流程和粒子运动的分离。

4、怎么运动?粒子自己最清楚

举个例子,gif图中,下落效果的粒子是这样运动的

public class FallingParticle extends Particle{
static Random random = new Random();
float radius = FallingParticleFactory.PART_WH;
float alpha = 1.0f;
Rect mBound;
/**
* @param color 颜色
* @param x
* @param y
*/
public FallingParticle(int color, float x, float y,Rect bound) {
super(color, x, y);
mBound = bound;
}
...
protected void caculate(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());
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

caculate(float factor)方法,根据当前时间,计算粒子的下一个位置 
我们可以看到,在这个粒子中,cy也就是竖直方向上是不断增加的,cx也就是水平方向上,是随机增加或者减少,这样就形成了下落效果。 
计算出当前位置以后,粒子就将自己绘制出来

protected void draw(Canvas canvas,Paint paint){
paint.setColor(color);
paint.setAlpha((int) (Color.alpha(color) * alpha)); //这样透明颜色就不是黑色了
canvas.drawCircle(cx, cy, radius, paint);
}
  • 1
  • 2
  • 3
  • 4
  • 5

怎么扩展?

从上面的代码结构可以看出,爆炸流程和粒子具体运动无关,最重要的是,我们要实现自己的caculate()方法,决定粒子的运动形态。 
而不同的粒子可以由对应的工厂产生,所以要扩展爆炸特性,只需要定义一个粒子类,和生成粒子类的工厂即可。

源码下载

原文地址:http://blog.csdn.net/crazy__chen/article/details/50149619

Android制作粒子爆炸特效的更多相关文章

  1. Android特效专辑(五)——自定义圆形头像和仿MIUI卸载动画—粒子爆炸

    Android特效专辑(五)--自定义圆形头像和仿MIUI卸载动画-粒子爆炸 好的,各位亲爱的朋友,今天讲的特效还是比较炫的,首先,我们会讲一个自定义圆形的imageView,接着,我们会来实现粒子爆 ...

  2. 7款让人惊叹的HTML5粒子动画特效(转载)

    1.HTML5 Canvas粒子模拟效果 这是一款利用HTML5 Canvas模拟出来的30000个粒子动画,当你用鼠标在canvas画布上移动时,鼠标周围的一些粒子就会跟着你移动,并形成一定的图案, ...

  3. 7款让人惊叹的HTML5粒子动画特效

    HTML5的很大一个优势就是可以更加便捷高效地制作网页粒子动画特效,特别是Canvas特性,可以实现在网页上绘制任何图形和动画.本文要分享7款让人惊叹的HTML5粒子动画特效,这些粒子特效都提供源代码 ...

  4. 基于HTML5 SVG炫酷文字爆炸特效

    这是一款使用html5 svg.css3和js制作的炫酷文字爆炸特效.该文字特效用SVG path属性将文字路径切割为很多小块,然后使用css3和js在鼠标滑过文字时制作文字爆炸分裂的炫酷效果. 在线 ...

  5. 多种的android进度条的特效源码

    多种的android进度条的特效源码,这个源码是在源码天堂那个网站上转载过来的,我已经修改一部分了,感觉很实用的,大家可以学习一下吧,我就不上传源码了,大家可以直接到那个网站上下载吧. 源码天堂下载地 ...

  6. 从零开始学Xamarin.Forms(三) Android 制作启动画面

    原文:从零开始学Xamarin.Forms(三) Android 制作启动画面     Xamarin.Forms 在启动的时候相当慢,必须添加一个启动界面,步骤如下: 1.将启动画面的图片命名为:s ...

  7. 用ESP8266+android,制作自己的WIFI小车(Android 软件)

    先说一下这篇文章里面的内容:TCP 客户端, 自定义对话框, 自定义按钮, ProgressBar竖直显示, 重力感应传感器,手机返回键新开启界面的问题(返回上次的界面),数据保存 软件的通信是配合 ...

  8. xamarin android制作圆角边框

    xamarin android制作圆角边框 效果图如下: 关键代码: drawable文件夹新建shape_corner_down.xml <?xml version="1.0&quo ...

  9. android系统联系人分组特效实现(2)---字母表快速滚动

    要实现这种功能,只需要在   android系统联系人分组特效实现(1)---分组导航和挤压动画  的基础上再加上一个自定义控件即可完成. 1.新建项目,继续新建一个java类,BladeView,用 ...

随机推荐

  1. Mvc Model 模板的获取【学习笔记】

    MVC的Model模板有两种:一种编辑模式(@Html.EditorFor()).一种显示模式(Html.DisplayFor()). 模板的获取与执行(以下转自这里): 当我们调用HtmlHelpe ...

  2. Asp.net MVC的Controller激活理解【学习笔记】

    DefaultControllerFactory 是MVC默认的Controller查找和激活工厂类我们可以通过自定义ControllerFactory替换DefaultControllerFacto ...

  3. nodejs compressor

    http://www.2cto.com/kf/201203/122015.html http://www.cnblogs.com/terrylin/archive/2013/06/01/3112596 ...

  4. asp.net关于Cookie跨域(域名)的问题

    Cookie是一个伟大的发明,它允许Web开发者保留他们的用户的登录状态.但是当你的站点有一个以上的域名时就会出现问题了.在Cookie规范上 说,一个cookie只能用于一个域名,不能够发给其它的域 ...

  5. 如何启用Service,如何停用Service

    一.步骤 第一步:继承Service类 public class SMSService extends Service { } 第二步:在AndroidManifest.xml文件中的<appl ...

  6. python换行写入文件

    今天用python做写入文件时,碰到,写入的东西不能换行,打开写入的文件都是一行.后来发现需要在写入的字符后面加上+'\n'. 另外python需要追加写入文件的时候,是用这个方法f = open(' ...

  7. Spring - Web MVC简介

    Web MVC简介 1.1.Web开发中的请求-响应模型: 在Web世界里,具体步骤如下: 1.  Web浏览器(如IE)发起请求,如访问http://www.cnblogs.com 2.  Web服 ...

  8. 【转】iOS 开发怎么入门?

    原文网址:http://www.zhihu.com/question/20264108 iOS 开发怎么入门? 请问有设计模式.内存管理方面的资料吗?最好有除了官方文档之外的其它内容,10 条评论 分 ...

  9. mkimage使用详解

    uboot源代码的tools/目录下有mkimage工具,这个工具可以用来制作不压缩或者压缩的多种可启动映象文件. mkimage在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个0x40字 ...

  10. HDOJ/HDU 2535 Vote(排序、)

    Problem Description 美国大选是按各州的投票结果来确定最终的结果的,如果得到超过一半的州的支持就可以当选,而每个州的投票结果又是由该州选民投票产生的,如果某个州超过一半的选民支持希拉 ...