Android粒子爆炸特效[可用于任意控件]

小米手機用戶可以看到,小米手機在應用卸載時會有一個粒子爆炸的特效效果,對此類動畫效果垂涎已久,奈何一直沒有機會用。正好最近項目里需要用到粒子爆炸的特效,于是便抽時間自己也試著仿寫了一個效果出來。

粒子爆炸

先看下效果:

粒子特效

How to use:



Step 1. Add the JitPack repository to your build file
Add it in your root build.gradle at the end of repositories:

    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

Step 2.Add the dependency

    dependencies {
            compile 'com.github.zhaolei9527:Particle-master:v1.0.1'
    }

代碼中這樣使用:

首先提供了響應式觸發的方式,首先進行埋雷,當控件被點擊時,觸發爆炸。是不是很刺激?

   //目前提供了六種的粒子爆炸特效
        explosionSite1 = new ExplosionSite(this, new BooleanFactory());
        explosionSite2 = new ExplosionSite(this, new ExplodeParticleFactory());
        explosionSite3 = new ExplosionSite(this, new FallingParticleFactory());
        explosionSite4 = new ExplosionSite(this, new FlyawayFactory());
        explosionSite5 = new ExplosionSite(this, new InnerFallingParticleFactory());
        explosionSite6 = new ExplosionSite(this, new VerticalAscentFactory());

        //爆炸激活方式一:將View或ViewGroup添加至雷管監聽,View被點擊時,觸發爆炸
        explosionSite1.addListener(img_1);
        explosionSite2.addListener(img_2);
        explosionSite3.addListener(img_6);
        explosionSite4.addListener(img_4);
        explosionSite5.addListener(img_5);
        explosionSite6.addListener(img_3);

其次針對一些情況,提供了另外一種直接的觸發方式。是不是更刺激?

                //爆炸激活方式二:將View或ViewGroup直接點燃爆炸
                explosionSite1.explode(img_1);
                explosionSite2.explode(img_2);
                explosionSite3.explode(img_3);
                explosionSite4.explode(img_4);
                explosionSite5.explode(img_5);
                explosionSite6.explode(img_6);

看下原理:GitHub項目地址
網上實現類似相同效果的很多,基本規則也都差不多。
主要對象如下:
ExplosionSite:爆炸效果發生的場地,是一個View。當一個控件需要爆炸時,需要為控件生成一個ExplosionSite,這個ExplosionSite覆蓋整個屏幕,于是我們才能看到完整的爆炸效果,在ExplosionField的構造函數中,傳入不同的ParticleFactory,就可以生成不同的爆炸效果。

ExplosionAnimator:爆炸動畫,其實是一個計時器,繼承自ValueAnimator。0x400s內,完成爆炸動畫,每次計時,就更新所有粒子的運動狀態。draw()方法是它最重要的方法,也就是使所有粒子重繪自身,從而實現動畫效果。

ParticleFactory:是一個抽象類。用于產生粒子數組,不同的ParticleFactory可以產生不同類型的粒子數組。

Particle:抽象的粒子類。代表粒子本身,必須擁有的屬性包括,當前自己的cx,cy坐標和顏色color。必須實現兩個方法,draw()方法選擇怎么繪制自身(圓形還是方形等),caculate()計算當前時間,自己所處的位置。


實現原理:
1、獲取當前控件背景bitmap
例如,例子中使用的是imageview,對于控件,在Utils中提供有一個工具類,可以獲得其背景的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;
        }
    }

2、將View鏡像轉換成粒子對象
獲取Bitmap以后,我們交給工廠對象進行加工,根據Bitmap生產Particle數組。
我們知道Bitmap可以看成是一個像素矩陣,矩陣上面的點,就是一個個帶有顏色的像素,于是我們可以獲取到每個點的顏色和位置,組裝成一個對象Particle,這么一來,Particle就代表帶有顏色的點了。

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

簡單來說都一樣,就是拿要爆炸的View制作一個鏡像出來然后返回。

然后來看一個粒子效果的具體實現。

  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];
        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 + BooleanFactory.PART_WH * column;
                float y = bound.top + BooleanFactory.PART_WH * row;
                particles[row][column] = new BooleanParticle(color,x,y,bound);
            }
        }

        return particles;
    }

很簡單,其中Rect類型的bound,是代表原來View控件的寬高信息。
根據我們設定的每個粒子的大小,和控件的寬高,我們就可以計算出,有多少個粒子組成這個控件的背景。
我們取得每個粒子所在位置的顏色,位置,用于生產粒子,這就是BooleanParticle。

3、爆炸對象及主要流程

public class ExplosionSite extends View {
    private static final String TAG = "ExplosionField";
    private ArrayList<ExplosionAnimator> explosionAnimators;
    private HashMap<View, ExplosionAnimator> explosionAnimatorsMap;
    private OnClickListener onClickListener;
    private ParticleFactory mParticleFactory;

    public ExplosionSite(Context context, ParticleFactory particleFactory) {
        super(context);
        init(particleFactory);
    }

    public ExplosionSite(Context context, AttributeSet attrs, ParticleFactory particleFactory) {
        super(context, attrs);
        init(particleFactory);
    }

    private void init(ParticleFactory particleFactory) {
        explosionAnimators = new ArrayList<ExplosionAnimator>();
        explosionAnimatorsMap = new HashMap<View, ExplosionAnimator>();
        mParticleFactory = particleFactory;
        attach2Activity((Activity) getContext());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (ExplosionAnimator animator : explosionAnimators) {
            animator.draw(canvas);
        }
    }

    /**
     * 爆破
     *
     * @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);//去掉狀態欄高度和標題欄高度
        if (rect.width() == 0 || rect.height() == 0) {
            return;
        }

        //震動動畫
        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();
    }

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

    /**
     * 給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);
    }


    /**
     * 希望誰有破碎效果,就給誰加Listener
     *
     * @param view 可以是ViewGroup
     */
    public void addListener(View view) {
        if (view instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) view;
            int count = viewGroup.getChildCount();
            for (int i = 0; i < count; i++) {
                addListener(viewGroup.getChildAt(i));
            }
        } else {
            view.setClickable(true);
            view.setOnClickListener(getOnClickListener());
        }
    }

    private OnClickListener getOnClickListener() {
        if (null == onClickListener) {
            onClickListener = new OnClickListener() {
                @Override
                public void onClick(View v) {
                    ExplosionSite.this.explode(v);
                }
            };
        }
        return onClickListener;
    }
}

總結:總體來說實現很簡單,就是根據工廠類,生成粒子數組。
而其實質是一個ValueAnimator,在一定時間內,從0數到1。
然后提供了一個draw()方法,方法里面調用了每個粒子的advance()方法,并且傳入了當前數到的數字。
advance()方法里,其實調用了draw()方法和caculate()方法。

上面的實現,其實是一個固定的流程,添加了爆炸場地以后,我們就開始從0數到1,在這個過程中,粒子會根據當前時間,繪制自己的位置,所以粒子的位置,其實是它自己決定的,和流程無關。
也就是說,我們只要用不同的算法,繪制粒子的位置即可,實現了流程和粒子運動的分離。
所以除了提供的六種爆炸特效之外,只要遵循這個原則,就能生成更多的粒子爆炸特效。

有了需求才有了功能,有了想法才有了創作,你的反饋會是使我進步的最大動力。
覺得還不夠方便?還想要什么功能?告訴我!歡迎反饋,歡迎Star。源碼入口:GitHub項目地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容