先上图:
这是一个高级UI特效,是个动画。
完成这个动画只要3步:
1、控件完成振动效果动画。
2、控件振动动画完成后消失,然后将控件转换成Bitmap.
3、Bitmap完成粒子爆炸特效。
其实完成粒子爆炸特效的并不是控件View本身,而是Bitmap,将Bitmap分割成多个小球,小球在x轴上随机地左右晃动,在y轴上径直下落就行了。
代码介绍:
Particle:
public abstract class Particle {
float x;
float y;
int color;
public Particle(float x, float y, int color) {
this.x = x;
this.y = y;
this.color = color;
}
//计算
protected abstract void compute(float factor);
//绘制
protected abstract void draw(Canvas canvas,Paint paint);
//逐步绘制
protected void draw(Canvas canvas,Paint paint,float factor){
compute(factor);
draw(canvas,paint);
}
}
这是一个粒子的抽象类,用于定义粒子的x,y坐标,颜色,计算x,y坐标的方法,绘制方法以及一个将这2个方法封装起来的新方法.
FallingParticle
public class FallingParticle extends Particle {
private static final String TAG = "FallingParticle";
private float radius = FallingParticleFactory.PART_WH; //粒子半径
private float alpha = 1.0f; //透明度
private Rect mBound;
public FallingParticle(float x, float y, int color,Rect bound) {
super(x, y, color);
mBound = bound;
}
@Override
protected void compute(float factor) {
x = x + factor * Utils.RANDOM.nextInt(mBound.width()) * (Utils.RANDOM.nextFloat() - 0.5f);
y = y + factor * Utils.RANDOM.nextInt(mBound.height() / 2);
radius = radius - factor * Utils.RANDOM.nextInt(2);
alpha = (1 - factor) * (1 + Utils.RANDOM.nextFloat());
}
@Override
protected void draw(Canvas canvas, Paint paint) {
paint.setColor(color);
paint.setAlpha((int) (Color.alpha(color) * alpha));
canvas.drawCircle(x,y,radius,paint);
}
}
继承抽象方法Particle,主要具体计算粒子的x、y坐标,半径,透明度,具体算法如下:
x = x +(0-1↑) × [-0.5width,0.5width] ():表示从从0到1逐步上升的数,[]:表示这个区间内的任意一个随机数(下同)
y = y + (0-1↑) × [0,0.5h]
radius = radius - (0-1↑) × [0,2]
alpha = [0-1↓] × (1 + [0,1])
粒子工厂抽象方法
public abstract class ParticleFactory {
protected abstract Particle[][] generateParticles(Bitmap bitmap, Rect bound);
}
用于生成粒子矩阵,只有一个方法用来生成所有粒子
粒子工厂
public class FallingParticleFactory extends ParticleFactory {
private static final String TAG = "FallingParticleFactory";
public static final int PART_WH = 8; //粒子默认宽高
@Override
protected Particle[][] generateParticles(Bitmap bitmap, Rect bound) {
int w = bound.width();
int h = bound.height();
Log.d(TAG, "generateParticles: " + bound.left + "," + bound.top);
int partW_count = w / PART_WH; //横向个数
int partH_count = h / PART_WH; //竖向个数
//判断个数是否小于1,这种情况是控件的大小 < 8
partW_count = partW_count > 1 ? partW_count : 1;
partH_count = partH_count > 1 ? partH_count : 1;
int bitmap_part_w = bitmap.getWidth() / partW_count;
int bitmap_part_h = bitmap.getHeight() / partH_count;
Particle[][] particles = new Particle[partH_count][partW_count];
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 + column * PART_WH;
float y = bound.top + row * PART_WH;
particles[row][column] = new FallingParticle(x,y,color,bound);
}
}
return particles;
}
}
将一张Bitmap分割成多个粒子,每个粒子的宽、高为8(这个可以自己设定),Rect是包裹这个View的外围矩形,通过它的宽、高除以粒子的宽高后就可以得到每行有多少粒子,一共有多少行。
ExplosionField
public class ExplosionField extends View {
private static final String TAG = "ExplosionField";
private List<ExplosionAnimator> explosionAnimators;
private OnClickListener onClickListener;
public ExplosionField(Context context) {
super(context);
explosionAnimators = new ArrayList<>();
//将动画区域添加到界面上
attachToActivity();
}
private void attachToActivity() {
//content是一个帧布局
ViewGroup rootView = ((Activity) getContext()).getWindow().getDecorView().findViewById(android.R.id.content);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
rootView.addView(this,params);
}
/**
* 添加需要有粒子特效的View
* @param view
*/
public void addListener(View view){
if(view instanceof ViewGroup){
ViewGroup viewGroup = (ViewGroup) view;
int viewCount = viewGroup.getChildCount();
for (int i = 0; i < viewCount; i++) {
addListener(viewGroup.getChildAt(i));
}
}else{
view.setOnClickListener(getOnClickListener());
}
}
public OnClickListener getOnClickListener(){
if(onClickListener == null){
onClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
explode(v);
}
};
}
return onClickListener;
}
//执行爆炸特效
public void explode(final View view){
final Rect rect = new Rect();
view.getGlobalVisibleRect(rect); //获取View相对整个屏幕的位置
//标题栏高度
int titleHeight = ((ViewGroup) getParent()).getTop();
//状态栏高度
Rect frame = new Rect();
((Activity) getContext()).getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
//相对整个屏幕的位置-标题栏(actionBar)的高度-状态栏的高度
rect.offset(0,-titleHeight-statusBarHeight);
if(rect.width() == 0 || rect.height() == 0){
return;
}
//震动动画
ValueAnimator animator = ValueAnimator.ofFloat(0f,1f).setDuration(150);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setTranslationX((Utils.RANDOM.nextFloat() - 0.5f) * view.getWidth() * 0.05f);
view.setTranslationY((Utils.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();
}
private void explode(final View view,Rect bound){
final ExplosionAnimator explosionAnimator = new ExplosionAnimator(this,Utils.createBitmapFromView(view),bound);
explosionAnimators.add(explosionAnimator);
explosionAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
view.setClickable(false);
//缩小透明
view.animate().setDuration(150).scaleX(0f).scaleY(0f).alpha(0f).start();
}
@Override
public void onAnimationEnd(Animator animation) {
view.setClickable(true);
//放大
view.animate().setDuration(150).scaleX(1f).scaleY(1f).alpha(1f).start();
explosionAnimators.remove(explosionAnimator);
}
});
explosionAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (ExplosionAnimator explosionAnimator : explosionAnimators) {
explosionAnimator.draw(canvas);
}
}
}
动画执行区域(其实是个View),首先将添加动画的控件设置点击监听器,如果这个控件是个ViewGroup,那么将它所有的子View设置点击监听器。紧接着执行振动动画,结束后执行粒子爆炸特效。
ExplosionAnimator
public class ExplosionAnimator extends ValueAnimator {
private static final String TAG = "ExplosionAnimator";
public static final int DEFAULT_DURATION = 1500; //动画默认持续时间
private Particle[][] mParticles; //粒子们
private ParticleFactory mParticleFactory; //粒子工厂
private View mContainer; //动画执行区域
private Paint mPaint;
public ExplosionAnimator(View view, Bitmap bitmap, Rect bound) {
mParticleFactory = new FallingParticleFactory();
mPaint = new Paint();
setFloatValues(0f,1f);
setDuration(DEFAULT_DURATION);
mParticles = mParticleFactory.generateParticles(bitmap,bound);
mContainer = view;
}
public void draw(Canvas canvas){
if(!isStarted()){
//动画结束
return;
}
//所有粒子开始运动
for (Particle[] mParticle : mParticles) {
for (Particle particle : mParticle) {
particle.draw(canvas,mPaint,(float) getAnimatedValue());
}
}
mContainer.invalidate();
}
@Override
public void start() {
super.start();
mContainer.invalidate();
Log.d(TAG, "start: ");
}
}
粒子爆炸动画实现类,继承于ValueAnimator,在draw方法中,遍历所以粒子并调用他们的draw方法,完成下一帧的绘制,值得注意的是,下方的invalidate方法会强制ExplosionField重绘,如图:
首先在ExplosionField中调用ExplosionAnimator的start方法,start方法中会使用invalidate方法来使ExplosionField重绘(调用onDraw方法),onDraw方法调用draw方法,draw方法中也使用invalidate,每一次循环完成一次重绘,整个动画就慢慢完成了。
demo地址:https://github.com/lyx19970504/Particle_Effect