一行代码实现任意View的粒子爆炸效果

使用方法很简单,只需要一行代码。

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;
        }
    }

思考:
其实还有很多可以扩展的地方,比如设置爆炸时粒子的形状,可以绘制成任意形状,粒子爆炸时扩展的方向、时间、大小等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值