Android 爆炸粒子动画-参考ExplosionField开源项目
explosion开源项目地址:https://github.com/ChanJLee/ExplosionField
由于没有理解到explosionField开源项目里的粒子爆炸后运动曲线计算公式,我这里使用了贝塞尔曲线作为粒子运动轨迹
在我的APP中应用效果图如下:
以下为此动画的核心类,继承自ValueAnimator
粒子在动画时长内沿着贝塞尔曲线运动:
曲线由起点,终点,和控制点决定,计算公式为:
这里,P0为起点,P1为控制点,P2为终点,t为属性动画的属性值,在动画时长内由0-1变化。
package com.konka.peter.bookentry.lib;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
//我自己的工具类,这里只用到了dp与px单位换算方法
import com.konka.peter.bookentry.util.Util;
import java.util.Random;
/**
* 类描述 :自定义爆炸动画类,CircleView中使用
* @author 王春涛
* @version 1.0.0
*/
public class ExplosionAnimator extends ValueAnimator {
//动画时长
private static final int DURATION=900;
//画笔
private Paint paint;
//所有爆炸后的粒子
private Particle[] particles;
//动画区域
private Rect rect;
//动画容器View
private View view;
//初始粒子数量num*num
private int num=6;
//每颗粒子半径
private static final float R= Util.dp2Px(2);
//随机变量
private Random random;
//粒子颜色
private int [] colors={0xf5f7fa,0xeaf2bf,0xf5f5eb,0xd1e69c,0xffffff};
public ExplosionAnimator(View view,Rect rect){
paint=new Paint();
this.rect=new Rect(rect);
//根据动画区域大小,计算粒子数量
num=Util.px2Dp(rect.right-rect.left)/12;
if(num<2){
num=2;
}
particles=new Particle[num*num];
random=new Random(System.currentTimeMillis());
//初始化粒子
for(int i=0;i<num;i++){
for(int j=0;j<num;j++){
int color=colors[random.nextInt(colors.length)];
particles[i*num+j]=initParticle(color);
}
}
this.view=view;
//设置动画取值范围
setFloatValues(0f,1f);
//动画速率曲线
setInterpolator(new AccelerateInterpolator(0.6f));
setDuration(DURATION);
}
/**
* 方法描述: 绘制每一颗粒子
* @param canvas 画布
* @return boolean
*/
public boolean draw(Canvas canvas){
for (Particle particle:particles){
//根据当前属性值,定位粒子在贝塞尔曲线上的坐标,getAnimatedValue获取当前动画属性值
particle.update((float) getAnimatedValue());
//绘制粒子
if(particle.alpha>0f){
paint.setColor(particle.color);
paint.setAlpha((int) particle.alpha);
canvas.drawCircle(particle.x,particle.y,particle.r,paint);
}
}
//绘制完,刷新view显示,此时动画与父View之间实现了串联互动
view.invalidate();
return true;
}
/**
* 方法描述: 动画开始,刷新页面
* @param
* @return
*/
@Override
public void start() {
super.start();
view.invalidate();
}
/**
* 方法描述: 初始化一颗粒子
* @param color 粒子颜色
* @return Particle 粒子
*/
public Particle initParticle(int color){
//自定义的粒子类
Particle particle=new Particle();
particle.color=color;
particle.r=R;
//start小于0.5,粒子向右运动,大于等于0.5,粒子向左运动
float start=random.nextFloat();
//粒子轨迹终点的随机偏移
float end=random.nextFloat();
//每颗粒子轨迹的起点都是固定的
particle.start_x=(rect.right+rect.left)/2;
particle.start_y=(rect.bottom+rect.top)/2;
//粒子终点则随机产生计算
if(start<0.5){ //向右
particle.end_x=(rect.right+rect.left)/2+(rect.right-rect.left)*end;
particle.end_y=rect.bottom+20*end;
}else { //向左
particle.end_x=(rect.right+rect.left)/2-(rect.right-rect.left)*(1-end);
particle.end_y=rect.bottom-20*(1-end);
}
//贝塞尔曲线的控制点,由轨迹起点,终点以及0.7和3两个可变数决定
particle.control_x=particle.start_x+0.7f*(particle.end_x-particle.start_x);
particle.control_y=particle.start_y-3*random.nextInt((int) (particle.end_y-particle.start_y));
return particle;
}
/**
* 类描述: 一颗粒子的实体类
*
*/
private class Particle{
//透明度
float alpha;
//颜色
int color;
//x坐标
float x;
//y坐标
float y;
//半径
float r;
//运行轨迹开始坐标
float start_x;
float start_y;
//运行轨迹结束坐标
float end_x;
float end_y;
//贝塞尔曲线控制点坐标
float control_x;
float control_y;
/**
* 方法描述: 初始化一颗粒子
* @param index 此刻动画的属性值
* @return Particle 粒子
*/
public void update(float index){
//贝塞尔曲线计算此刻粒子坐标,根据贝塞尔曲线公式
x=(1-index)*(1-index)*start_x+2*index*(1-index)*control_x+index*index*end_x;
y=(1-index)*(1-index)*start_y+2*index*(1-index)*control_y+index*index*end_y;
//透明度
if(index<0.8){
alpha=255;
}else {
alpha=255*(-5*index+5);
}
}
}
}
接下来我们在一个自定义View里面使用以上动画:
package com.konka.peter.bookentry.lib;
import android.animation.Animator;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.View;
/**
* 类描述 :自定义View
* @author 王春涛
* @version 1.0.0
*/
public class CircleView extends View {
//粒子爆炸动画
private ExplosionAnimator explosionAnimator;
public CircleView(Context context) {
super(context);
//动画开始
startExplosionAnim();
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
//动画开始
startExplosionAnim();
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//动画开始
startExplosionAnim();
}
/**
* 方法描述: 重写绘制方法,在这里绘制爆炸过程的每一帧
* @param canvas 画布
* @return void
*
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
explosionAnimator.draw(canvas);
}
private void startExplosionAnim(){
//动画显示区域
Rect rect=new Rect(0,0,200,200);
explosionAnimator=new ExplosionAnimator(this,rect);
explosionAnimator.start();
}
}