使用方法很简单,只需要一行代码。
BombViewUtil.bomb(view,null);//该view需要展现粒子爆炸效果时调用
比如对于常见的ImageView、Button、TextView调用上面的方法,效果如下。
分析实现:
1、首先要明白爆炸的不是真正的TextView或者Button,而是他们对应的快照。通过下面这个方法就可以获得某个View对应的Bitmap。
target.setDrawingCacheEnabled(true);
target.buildDrawingCache();
Bitmap bitmap=target.getDrawingCache();
2、当调用BombViewUtil.bomb(view,null)后,真正的View应该隐藏(GONE),而显示我们自定义的BombView,这个BombView负责绘制显示真正的View对应的Bitmap。
if (!mBitmap.isRecycled()){
canvas.drawBitmap(mBitmap,mInitX,mInitY,mPaint);
}
3、将Bitmap设置给BombView时,应该遍历获取其中的像素,每个像素对应的颜色、位置信息封装到一个粒子实体。
/**
* 描述:粒子实体
*/
public class Granule {
/**粒子圆心坐标*/
public float x;
public float y;
/**粒子圆半径*/
public float r;
/**粒子圆像素值*/
public int pixel;
/**粒子圆x,y方向的速度*/
public float vX;
public float vY;
/**粒子圆x,y方向的加速度*/
public float aX;
public float aY;
}
//init 方法中的部分代码
int row = 0,col=0;
for (int i = 0; i < mBitmap.getHeight(); i++) {
if (i%10!=0){//优化 只取0,10 ,20...行的像素 其他行丢弃
continue;
}
for (int j = 0; j < mBitmap.getWidth(); j++) {
if (j%10!=0){//优化 只取 0,10,20...列的像素,其他列丢弃
continue;
}
//获取像素矩阵对应像素的颜色值
int pixel=mBitmap.getPixel(j,i);
//创建一个代表粒子的对象,封装必要的信息
Granule granule=new Granule();
granule.x=mInitX+col*mD+mD/2;
granule.y=mInitY+row*mD+mD/2;
granule.r=mD/2;
granule.pixel=pixel;
granule.vX= random.nextInt(10) - 5.0f;
granule.vY= random.nextInt(10) - 5.0f;
granule.aX= 0;
granule.aY= 0.98f;
mList.add(granule);
++col;
}
col=0;
++row;
}
4、发生爆炸时,采用的是值动画,在一定时间不断遍历粒子集合改变他们的圆心坐标,和竖直方向的速度。
for (Granule granule : mList) {
granule.x+=granule.vX;
granule.y+=granule.vY;
granule.vY+=granule.aY;
}
粒子位置信息改变后调用invalidate();重绘BombView。
for (Granule granule : mList) {
mPaint.setColor(granule.pixel);
canvas.drawCircle(granule.x,granule.y,granule.r,mPaint);
}
5、爆炸动画结束,通知出去,移除所有的View。
这里为什么要通过i%10!=0来优化,主要是像素的数量太多了,如果一个图片的大小为144*144个像素,那就代表集合中要装20736个对象,当发生粒子动画的过程会不断的遍历集合中所有的元素,改变它们的位置信息,然后在onDraw方法中又不断的遍历绘制它们,想想就感觉分分钟卡顿的节奏。所以这里我每隔10个像素点取一个。
实现起来其实很简单,完整代码如下:
//Granule start
/**
* 描述:粒子
*/
public class Granule {
/**粒子圆心坐标*/
public float x;
public float y;
/**粒子圆半径*/
public float r;
/**粒子圆像素值*/
public int pixel;
/**粒子圆x,y方向的速度*/
public float vX;
public float vY;
/**粒子圆x,y方向的加速度*/
public float aX;
public float aY;
}
//Granule end
//BombView start
/**
* 描述:会爆炸的View
*/
public class BombView extends View{
private Bitmap mBitmap;
//粒子的直径
private float mD=6;
private List<Granule> mList;
private Paint mPaint;
private ValueAnimator mValueAnimator;
private boolean mBombStatus;
//初始位置
private int mInitX;
private int mInitY;
public BombView(Context context) {
super(context,null);
}
public BombView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public BombView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void attachTarget(Bitmap snapshot, int[] loc) {
mBitmap=snapshot;
mInitX=loc[0];
mInitY=loc[1];
init();
}
private void init() {
mPaint=new Paint();
mList=new ArrayList<>();
Random random = new Random();
int row = 0,col=0;
for (int i = 0; i < mBitmap.getHeight(); i++) {
if (i%10!=0){//优化
continue;
}
for (int j = 0; j < mBitmap.getWidth(); j++) {
if (j%10!=0){//优化
continue;
}
//获取像素矩阵对应像素的颜色值
int pixel=mBitmap.getPixel(j,i);
Granule granule=new Granule();
granule.x=mInitX+col*mD+mD/2;
granule.y=mInitY+row*mD+mD/2;
granule.r=mD/2;
granule.pixel=pixel;
granule.vX= random.nextInt(10) - 5.0f;
granule.vY= random.nextInt(10) - 5.0f;
granule.aX= 0;
granule.aY= 0.98f;
mList.add(granule);
++col;
}
col=0;
++row;
}
mValueAnimator=ValueAnimator.ofFloat(0,1);
mValueAnimator.setDuration(3000);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateGranules();
invalidate();
}
});
mValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (mOnBombCallback!=null){
mOnBombCallback.onBombEnd();
}
}
@Override
public void onAnimationStart(Animator animation) {
if (mOnBombCallback!=null){
mOnBombCallback.onBombStart();
}
}
});
}
/**更新粒子位置和速度*/
private void updateGranules() {
for (Granule granule : mList) {
granule.x+=granule.vX;
granule.y+=granule.vY;
granule.vY+=granule.aY;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBombStatus){
for (Granule granule : mList) {
mPaint.setColor(granule.pixel);
canvas.drawCircle(granule.x,granule.y,granule.r,mPaint);
}
}else {
if (!mBitmap.isRecycled()){
canvas.drawBitmap(mBitmap,mInitX,mInitY,mPaint);
}
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mValueAnimator!=null){
mValueAnimator.cancel();
mValueAnimator.removeAllUpdateListeners();
mValueAnimator=null;
}
}
public void bomb() {
mValueAnimator.start();
mBombStatus=true;
}
public interface OnBombCallback{
void onBombStart();
void onBombEnd();
}
private OnBombCallback mOnBombCallback;
public void addCallback(OnBombCallback callback){
this.mOnBombCallback=callback;
}
}
//BombView end
//BombViewUtil start
/**
* 描述:BombView工具类
*/
public class BombViewUtil {
public static void bomb(View target,OnBombListener bombListener){
if (target == null){
return;
}
new Build(target,bombListener).build();
}
static class Build {
private WindowManager mWindowManager;
private final View mTarget;
private FrameLayout mFullScreenLayout;
private WindowManager.LayoutParams mWindowLayoutParams;
private BombView mBombView;
private OnBombListener mOnBombListener;
public Build(final View target, final OnBombListener bombListener){
mTarget=target;
mOnBombListener=bombListener;
mWindowManager= (WindowManager) mTarget.getContext().getSystemService(Context.WINDOW_SERVICE);
mWindowLayoutParams=new WindowManager.LayoutParams();
mWindowLayoutParams.format= PixelFormat.TRANSPARENT;
//BombView实际上是显示在mFullScreenLayout这个容器中的
mFullScreenLayout=new FrameLayout(mTarget.getContext());
mFullScreenLayout.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mBombView=new BombView( mTarget.getContext());
}
//获取target View对应的Bitmap
private Bitmap getTargetViewSnapshot(View target) {
target.setDrawingCacheEnabled(true);
target.buildDrawingCache();
Bitmap bitmap=target.getDrawingCache();
return bitmap;
}
//获取target View在屏幕的位置
private int[] getTargetLocationOnScreen(View target) {
int[] outLocation=new int[2];
target.getLocationOnScreen(outLocation);
return outLocation;
}
public void build() {
int[] loc=getTargetLocationOnScreen(mTarget);
Bitmap snapshot=getTargetViewSnapshot(mTarget);
//将snapshot传给BombView
mBombView.attachTarget(snapshot,loc);
mFullScreenLayout.addView(mBombView);
mWindowManager.addView(mFullScreenLayout,mWindowLayoutParams);
mBombView.addCallback(new BombView.OnBombCallback() {
@Override
public void onBombStart() {
notifyOnBombStart();
}
@Override
public void onBombEnd() {
//动画结束后移除所有的view
mFullScreenLayout.removeAllViews();
if (mWindowManager!=null){
mWindowManager.removeView(mFullScreenLayout);
mWindowManager=null;
}
notifyOnBombEnd();
}
});
//让原本的view不可见
mTarget.setVisibility(View.GONE);
mBombView.bomb();
}
private void notifyOnBombStart() {
if (mOnBombListener!=null){
mOnBombListener.onBombStart();
}
}
private void notifyOnBombEnd() {
if (mOnBombListener!=null){
mOnBombListener.onBombEnd();
}
}
}
public interface OnBombListener{
void onBombEnd();
void onBombStart();
}
}
//BombViewUtil end
使用:
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn1:
BombViewUtil.bomb(view,null);
break;
case R.id.image1:
BombViewUtil.bomb(view,null);
break;
case R.id.text1:
BombViewUtil.bomb(view,null);
break;
case R.id.image2:
BombViewUtil.bomb(view,null);
break;
}
}
思考:
其实还有很多可以扩展的地方,比如设置爆炸时粒子的形状,可以绘制成任意形状,粒子爆炸时扩展的方向、时间、大小等。