自定義View 粒子效果

描述

效果圖:


image.png

要實(shí)現(xiàn)一張圖片的爆炸效果错邦,有幾個(gè)關(guān)鍵的點(diǎn):

第一點(diǎn)土榴、根據(jù)圖片的寬和高獲取每一個(gè)像素的點(diǎn)荸哟,并且根據(jù)這個(gè)像素點(diǎn)構(gòu)建在這一個(gè)像素點(diǎn)ball 對(duì)象锦积;

第二點(diǎn)芒帕、在獲取ball 的數(shù)組對(duì)象的時(shí)候,需要在子線程來(lái)做這個(gè)事情丰介,防止UI卡頓背蟆。

第三點(diǎn)、啟動(dòng)一個(gè)動(dòng)畫(huà)來(lái)循環(huán)的調(diào)用繪制哮幢。

第四點(diǎn)带膀、在繪制的時(shí)候需要把所有ball 都一一繪制。

創(chuàng)建一個(gè)粒子對(duì)象(Ball)橙垢。

    - 圖片像素點(diǎn)顏色值color
- 粒子圓心坐標(biāo)x
- 粒子圓心坐標(biāo)y
- 粒子半徑r
- 粒子運(yùn)動(dòng)水平方向速度vx
- 粒子運(yùn)動(dòng)垂直方向速度vy
- 粒子運(yùn)動(dòng)水平方式加速度ax
- 粒子運(yùn)動(dòng)垂直方向加速度ay
    public int color; //圖片像素點(diǎn)顏色值
    public float x; //粒子圓心坐標(biāo)x
    public float y; //粒子圓心坐標(biāo)y
    public float r; //粒子半徑

    public float vX;//粒子運(yùn)動(dòng)水平方向速度
    public float vY;//粒子運(yùn)動(dòng)垂直方向速度
    public float aX;//粒子運(yùn)動(dòng)水平方向加速度
    public float aY;//粒子運(yùn)動(dòng)垂直方向加速度

創(chuàng)建一個(gè)自定義view 垛叨,組合粒子。

 - 初始畫(huà)筆Paint柜某,bitmap對(duì)象嗽元,粒子直徑(float類型)
     paint = new Paint();
     mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
 - 獲取所有的粒子集合
//計(jì)算粒子數(shù)
    private void calculateBalls() {
        if (mBitmap == null || mBitmap.isRecycled()) return;
        int width = mBitmap.getWidth();
        int height = mBitmap.getHeight();
        for (int i = 0; i < width; i += ballPoor) {
            for (int j = 0; j < height; j += ballPoor) {
                Ball ball = new Ball();

                int realWidth = ballPoor;
                int realHeight = ballPoor;
                if (i + realWidth > width) {
                    realWidth = width - i;
                }
                if (j + realHeight > height) {
                    realHeight = height - j;
                }
                //這里的像素值,不準(zhǔn)確
                int[] colors = new int[realWidth * realHeight];
//            @param pixels   接收位圖顏色的數(shù)組
//            @param offset   第一個(gè)要寫(xiě)入像素的索引[]
//            @param stride   以像素[]為單位的項(xiàng)數(shù)喂击,可在其中跳過(guò)行(必須是>=位圖的寬度)剂癌。可以是負(fù)的翰绊。
//            @param x        要讀取的第一個(gè)像素的x坐標(biāo)
//            @param y        要讀取的第一個(gè)像素的y坐標(biāo)
//            @param width    從每一行讀取的像素?cái)?shù)
//            @param height   要讀取的行數(shù)
                mBitmap.getPixels(colors, 0, realWidth, i, j, realWidth, realHeight);
                int color = getColor(colors);
                ball.setColor(color);
                //線性增加多少佩谷,就縮小多少倍,這種來(lái)計(jì)算
                ball.x = i * d / ballPoor + d / 2;
                ball.y = j * d / ballPoor + d / 2;
                ball.setR((float) d / 2);
                //x方向速度20或者-20
                ball.setvX((float) (Math.pow(-1, Math.ceil(Math.random() * 1000)) * 20 * Math.random()));
                ball.setvY(rangeInt(-10, 20));
                ball.setaX(0);
                ball.setaY(0.98f);
                ballList.add(ball);
            }
        }
    }
  • 初始動(dòng)畫(huà)辞做,并且監(jiān)聽(tīng)動(dòng)畫(huà)變更琳要,改變Ball的位置和invalidate();
    調(diào)用invalidate(),會(huì)執(zhí)行繪制的方法秤茅,onDraw();
//        創(chuàng)建一個(gè)線程池稚补,該線程池根據(jù)需要?jiǎng)?chuàng)建新線程,但是將重用以前構(gòu)造的線程可用框喳。這些池通常會(huì)提高性能
//        執(zhí)行許多短期異步任務(wù)的程序课幕。
//        對(duì){@code execute}的調(diào)用將重用前面構(gòu)造的
//        線程(如果可用)。如果沒(méi)有現(xiàn)有線程可用五垮,則使用一個(gè)新線程
//        線程將被創(chuàng)建并添加到池中乍惊。線程,
//        * 60秒內(nèi)未使用的將被終止并移除
//        *緩存。因此放仗,一個(gè)閑置足夠長(zhǎng)的池將會(huì)
//        *不消耗任何資源润绎。注意,池與類似
//        *屬性,但不同的細(xì)節(jié)(例如超時(shí)參數(shù))
        valueAnimator = ValueAnimator.ofFloat(animBeginValue, animEndValue);
        //線性插值器
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.setRepeatCount(0);//0   不重復(fù)莉撇,默認(rèn)也是0
        valueAnimator.setDuration(duration);
        valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationFinish(ValueAnimator animation) {
                //動(dòng)畫(huà)結(jié)束
                Log.i("ball", "動(dòng)畫(huà)結(jié)束");
            }
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //動(dòng)畫(huà)每一幀的改變
                float value = (float) animation.getAnimatedValue();
//                Log.i("ball","動(dòng)畫(huà)value=====" +value);
                if (value == animEndValue) {
                    //動(dòng)畫(huà)執(zhí)行結(jié)束
                    this.onAnimationFinish(animation);
                }
                //在動(dòng)畫(huà)改變的時(shí)候呢蛤,調(diào)用繪制
                updateBallState();
                invalidate();
            }
        });
  • 循環(huán)繪制所有的粒子
 for (Ball ball : ballList) {
            paint.setColor(ball.getColor());
            canvas.drawCircle(ball.getX(), ball.getY(), ball.getR(), paint);
        }

改進(jìn)

在布局文件中使用了自定義的view 的時(shí)候,寬度和高度棍郎,
如果沒(méi)有使用確切值的情況其障,默認(rèn)是填充滿屏幕的。

完整代碼:

package com.netease.canvas.split;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;

/**
 * 作者:liupeng
 * 創(chuàng)建時(shí)間:2019/4/26
 * 描述:優(yōu)化涂佃,確認(rèn)布局的寬和高度励翼,不然會(huì)出現(xiàn)問(wèn)題
 */
public class LZBallView extends View {
    //粒子的集合,通過(guò)獲取圖片的寬度或者view 的寬度來(lái)計(jì)算辜荠,子線程做這個(gè)事情哦
    private List<Ball> ballList = new ArrayList<>();
    private Paint paint;//畫(huà)筆

    //粒子的直徑.設(shè)置默認(rèn)值汽抚,后續(xù)通過(guò)屬性來(lái)設(shè)置
    private float d = 20;
    //動(dòng)畫(huà)
    private ValueAnimator valueAnimator;
    //動(dòng)畫(huà)時(shí)長(zhǎng),后面也是通過(guò)屬性配置的
    private long duration = 3000;

    private float animBeginValue = 0;
    private float animEndValue = 1;
    //圖片
    private Bitmap mBitmap;
    //用于控制粒子的個(gè)數(shù)伯病,如果是一個(gè)像素的一個(gè)像素的添加殊橙,那么粒子數(shù)目較多,默認(rèn)等于
    private int ballPoor = 10;

    //是否已經(jīng)開(kāi)始了動(dòng)畫(huà)
    private boolean startAnim;

    public LZBallView(Context context) {
        this(context, null);
    }

    public LZBallView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    //初始化操作
    private void init() {
        paint = new Paint();
        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
//        創(chuàng)建一個(gè)線程池狱从,該線程池根據(jù)需要?jiǎng)?chuàng)建新線程,但是將重用以前構(gòu)造的線程可用叠纹。這些池通常會(huì)提高性能
//        執(zhí)行許多短期異步任務(wù)的程序季研。
//        對(duì){@code execute}的調(diào)用將重用前面構(gòu)造的
//        線程(如果可用)。如果沒(méi)有現(xiàn)有線程可用誉察,則使用一個(gè)新線程
//        線程將被創(chuàng)建并添加到池中与涡。線程,
//        * 60秒內(nèi)未使用的將被終止并移除
//        *緩存。因此持偏,一個(gè)閑置足夠長(zhǎng)的池將會(huì)
//        *不消耗任何資源驼卖。注意,池與類似
//        *屬性鸿秆,但不同的細(xì)節(jié)(例如超時(shí)參數(shù))
        Executors.newCachedThreadPool().execute(new Runnable() {
            @Override
            public void run() {
                calculateBalls();
            }
        });
        valueAnimator = ValueAnimator.ofFloat(animBeginValue, animEndValue);
        //線性插值器
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.setRepeatCount(0);//0   不重復(fù)酌畜,默認(rèn)也是0
        valueAnimator.setDuration(duration);
        valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationFinish(ValueAnimator animation) {
                //動(dòng)畫(huà)結(jié)束
                Log.i("ball", "動(dòng)畫(huà)結(jié)束");
            }
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //動(dòng)畫(huà)每一幀的改變
                float value = (float) animation.getAnimatedValue();
//                Log.i("ball","動(dòng)畫(huà)value=====" +value);
                if (value == animEndValue) {
                    //動(dòng)畫(huà)執(zhí)行結(jié)束
                    this.onAnimationFinish(animation);
                }
                //在動(dòng)畫(huà)改變的時(shí)候,調(diào)用繪制
                updateBallState();
                invalidate();
            }
        });
    }

    //計(jì)算粒子數(shù)
    private void calculateBalls() {
        if (mBitmap == null || mBitmap.isRecycled()) return;
        int width = mBitmap.getWidth();
        int height = mBitmap.getHeight();
        for (int i = 0; i < width; i += ballPoor) {
            for (int j = 0; j < height; j += ballPoor) {
                Ball ball = new Ball();

                int realWidth = ballPoor;
                int realHeight = ballPoor;
                if (i + realWidth > width) {
                    realWidth = width - i;
                }
                if (j + realHeight > height) {
                    realHeight = height - j;
                }
                //這里的像素值卿叽,不準(zhǔn)確
                int[] colors = new int[realWidth * realHeight];
//            @param pixels   接收位圖顏色的數(shù)組
//            @param offset   第一個(gè)要寫(xiě)入像素的索引[]
//            @param stride   以像素[]為單位的項(xiàng)數(shù)桥胞,可在其中跳過(guò)行(必須是>=位圖的寬度)】加ぃ可以是負(fù)的贩虾。
//            @param x        要讀取的第一個(gè)像素的x坐標(biāo)
//            @param y        要讀取的第一個(gè)像素的y坐標(biāo)
//            @param width    從每一行讀取的像素?cái)?shù)
//            @param height   要讀取的行數(shù)
                mBitmap.getPixels(colors, 0, realWidth, i, j, realWidth, realHeight);
                int color = getColor(colors);
                ball.setColor(color);
                //線性增加多少,就縮小多少倍沥阱,這種來(lái)計(jì)算
                ball.x = i * d / ballPoor + d / 2;
                ball.y = j * d / ballPoor + d / 2;
                ball.setR((float) d / 2);
                //x方向速度20或者-20
                ball.setvX((float) (Math.pow(-1, Math.ceil(Math.random() * 1000)) * 20 * Math.random()));
                ball.setvY(rangeInt(-10, 20));
                ball.setaX(0);
                ball.setaY(0.98f);
                ballList.add(ball);
            }
        }
    }

    //獲取顏色
    private int getColor(int[] colors) {
        int ar = 0, ag = 0, ab = 0;
        for (int color : colors) {
            int r = (color & 0xff0000) >> 16;
            int g = (color & 0x00ff00) >> 8;
            int b = color & 0x0000ff;
            ar += r;
            ag += g;
            ab += b;
        }
        return Color.rgb(ar / colors.length, ag / colors.length, ab / colors.length);
    }

    private float rangeInt(int i, int j) {
        int max = Math.max(i, j);
        int min = Math.min(i, j) - 1;
        //在0到(max - min)范圍內(nèi)變化缎罢,取大于x的最小整數(shù) 再隨機(jī)
        return (int) (min + Math.ceil(Math.random() * (max - min)));
    }

    //動(dòng)畫(huà)執(zhí)行過(guò)程中,改變粒子的狀態(tài)
    private void updateBallState() {
        for (Ball ball : ballList) {
            ball.setX(ball.getX() + ball.getvX());
            ball.setY(ball.getY() + ball.getvY());
            ball.setvX(ball.getvX() + ball.getaX());
            ball.setvY(ball.getvY() + ball.getaY());
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            //執(zhí)行動(dòng)畫(huà)
            startAnim = true;
            valueAnimator.start();
        }
        return super.onTouchEvent(event);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int mHeight = MeasureSpec.getSize(heightMeasureSpec);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //繪制時(shí)間過(guò)長(zhǎng)會(huì)直接崩潰的
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        //畫(huà)布的平移操作
        if (width >= mBitmap.getWidth()) {
            if (height >= mBitmap.getHeight()){
                canvas.translate(width/2 -mBitmap.getWidth()/2, height/2 -mBitmap.getHeight()/2);
            }else{
                canvas.translate(width/2 -mBitmap.getWidth()/2, 0);
            }
        }else {
            //view 的寬度是小于圖片的寬度的什么都不做
            if (height >= mBitmap.getHeight()){
                canvas.translate(0, height/2 -mBitmap.getHeight()/2);
            }else{
                canvas.translate(0, 0);
            }

        }

        if (!startAnim) {
            canvas.drawBitmap(mBitmap, 0, 0, paint);
            return;
        }
        for (Ball ball : ballList) {
            paint.setColor(ball.getColor());
            canvas.drawCircle(ball.getX(), ball.getY(), ball.getR(), paint);
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (valueAnimator != null) {
            valueAnimator.cancel();
        }
    }

    interface AnimatorUpdateListener extends ValueAnimator.AnimatorUpdateListener {
        void onAnimationFinish(ValueAnimator animation);
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市策精,隨后出現(xiàn)的幾起案子舰始,更是在濱河造成了極大的恐慌,老刑警劉巖蛮寂,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔽午,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡酬蹋,警方通過(guò)查閱死者的電腦和手機(jī)及老,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)范抓,“玉大人骄恶,你說(shuō)我怎么就攤上這事∝暗妫” “怎么了僧鲁?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)象泵。 經(jīng)常有香客問(wèn)我寞秃,道長(zhǎng),這世上最難降的妖魔是什么偶惠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任春寿,我火速辦了婚禮,結(jié)果婚禮上忽孽,老公的妹妹穿的比我還像新娘绑改。我一直安慰自己,他們只是感情好兄一,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布厘线。 她就那樣靜靜地躺著,像睡著了一般出革。 火紅的嫁衣襯著肌膚如雪造壮。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,084評(píng)論 1 291
  • 那天骂束,我揣著相機(jī)與錄音费薄,去河邊找鬼。 笑死栖雾,一個(gè)胖子當(dāng)著我的面吹牛楞抡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播析藕,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼召廷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起竞慢,我...
    開(kāi)封第一講書(shū)人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤先紫,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后筹煮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體遮精,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年败潦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了本冲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡劫扒,死狀恐怖檬洞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沟饥,我是刑警寧澤添怔,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站贤旷,受9級(jí)特大地震影響广料,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜幼驶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一性昭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧县遣,春花似錦、人聲如沸汹族。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)顶瞒。三九已至夸政,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間榴徐,已是汗流浹背守问。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坑资,地道東北人耗帕。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像袱贮,于是被迫代替她去往敵國(guó)和親仿便。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容