自定義View 粒子效果

描述

效果圖:


image.png

要實現(xiàn)一張圖片的爆炸效果,有幾個關(guān)鍵的點:

第一點、根據(jù)圖片的寬和高獲取每一個像素的點,并且根據(jù)這個像素點構(gòu)建在這一個像素點ball 對象;

第二點、在獲取ball 的數(shù)組對象的時候,需要在子線程來做這個事情,防止UI卡頓。

第三點、啟動一個動畫來循環(huán)的調(diào)用繪制。

第四點、在繪制的時候需要把所有ball 都一一繪制。

創(chuàng)建一個粒子對象(Ball)。

    - 圖片像素點顏色值color
- 粒子圓心坐標x
- 粒子圓心坐標y
- 粒子半徑r
- 粒子運動水平方向速度vx
- 粒子運動垂直方向速度vy
- 粒子運動水平方式加速度ax
- 粒子運動垂直方向加速度ay
    public int color; //圖片像素點顏色值
    public float x; //粒子圓心坐標x
    public float y; //粒子圓心坐標y
    public float r; //粒子半徑

    public float vX;//粒子運動水平方向速度
    public float vY;//粒子運動垂直方向速度
    public float aX;//粒子運動水平方向加速度
    public float aY;//粒子運動垂直方向加速度

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

 - 初始畫筆Paint,bitmap對象,粒子直徑(float類型)
     paint = new Paint();
     mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
 - 獲取所有的粒子集合
//計算粒子數(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;
                }
                //這里的像素值,不準確
                int[] colors = new int[realWidth * realHeight];
//            @param pixels   接收位圖顏色的數(shù)組
//            @param offset   第一個要寫入像素的索引[]
//            @param stride   以像素[]為單位的項數(shù),可在其中跳過行(必須是>=位圖的寬度)??梢允秦摰摹?//            @param x        要讀取的第一個像素的x坐標
//            @param y        要讀取的第一個像素的y坐標
//            @param width    從每一行讀取的像素數(shù)
//            @param height   要讀取的行數(shù)
                mBitmap.getPixels(colors, 0, realWidth, i, j, realWidth, realHeight);
                int color = getColor(colors);
                ball.setColor(color);
                //線性增加多少,就縮小多少倍,這種來計算
                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);
            }
        }
    }
  • 初始動畫,并且監(jiān)聽動畫變更,改變Ball的位置和invalidate();
    調(diào)用invalidate(),會執(zhí)行繪制的方法,onDraw();
//        創(chuàng)建一個線程池,該線程池根據(jù)需要創(chuàng)建新線程,但是將重用以前構(gòu)造的線程可用。這些池通常會提高性能
//        執(zhí)行許多短期異步任務(wù)的程序。
//        對{@code execute}的調(diào)用將重用前面構(gòu)造的
//        線程(如果可用)。如果沒有現(xiàn)有線程可用,則使用一個新線程
//        線程將被創(chuàng)建并添加到池中。線程,
//        * 60秒內(nèi)未使用的將被終止并移除
//        *緩存。因此,一個閑置足夠長的池將會
//        *不消耗任何資源。注意,池與類似
//        *屬性,但不同的細節(jié)(例如超時參數(shù))
        valueAnimator = ValueAnimator.ofFloat(animBeginValue, animEndValue);
        //線性插值器
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.setRepeatCount(0);//0   不重復,默認也是0
        valueAnimator.setDuration(duration);
        valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationFinish(ValueAnimator animation) {
                //動畫結(jié)束
                Log.i("ball", "動畫結(jié)束");
            }
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //動畫每一幀的改變
                float value = (float) animation.getAnimatedValue();
//                Log.i("ball","動畫value=====" +value);
                if (value == animEndValue) {
                    //動畫執(zhí)行結(jié)束
                    this.onAnimationFinish(animation);
                }
                //在動畫改變的時候,調(diào)用繪制
                updateBallState();
                invalidate();
            }
        });
  • 循環(huán)繪制所有的粒子
 for (Ball ball : ballList) {
            paint.setColor(ball.getColor());
            canvas.drawCircle(ball.getX(), ball.getY(), ball.getR(), paint);
        }

改進

在布局文件中使用了自定義的view 的時候,寬度和高度,
如果沒有使用確切值的情況,默認是填充滿屏幕的。

完整代碼:

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)建時間:2019/4/26
 * 描述:優(yōu)化,確認布局的寬和高度,不然會出現(xiàn)問題
 */
public class LZBallView extends View {
    //粒子的集合,通過獲取圖片的寬度或者view 的寬度來計算,子線程做這個事情哦
    private List<Ball> ballList = new ArrayList<>();
    private Paint paint;//畫筆

    //粒子的直徑.設(shè)置默認值,后續(xù)通過屬性來設(shè)置
    private float d = 20;
    //動畫
    private ValueAnimator valueAnimator;
    //動畫時長,后面也是通過屬性配置的
    private long duration = 3000;

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

    //是否已經(jīng)開始了動畫
    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)建一個線程池,該線程池根據(jù)需要創(chuàng)建新線程,但是將重用以前構(gòu)造的線程可用。這些池通常會提高性能
//        執(zhí)行許多短期異步任務(wù)的程序。
//        對{@code execute}的調(diào)用將重用前面構(gòu)造的
//        線程(如果可用)。如果沒有現(xiàn)有線程可用,則使用一個新線程
//        線程將被創(chuàng)建并添加到池中。線程,
//        * 60秒內(nèi)未使用的將被終止并移除
//        *緩存。因此,一個閑置足夠長的池將會
//        *不消耗任何資源。注意,池與類似
//        *屬性,但不同的細節(jié)(例如超時參數(shù))
        Executors.newCachedThreadPool().execute(new Runnable() {
            @Override
            public void run() {
                calculateBalls();
            }
        });
        valueAnimator = ValueAnimator.ofFloat(animBeginValue, animEndValue);
        //線性插值器
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.setRepeatCount(0);//0   不重復,默認也是0
        valueAnimator.setDuration(duration);
        valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationFinish(ValueAnimator animation) {
                //動畫結(jié)束
                Log.i("ball", "動畫結(jié)束");
            }
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //動畫每一幀的改變
                float value = (float) animation.getAnimatedValue();
//                Log.i("ball","動畫value=====" +value);
                if (value == animEndValue) {
                    //動畫執(zhí)行結(jié)束
                    this.onAnimationFinish(animation);
                }
                //在動畫改變的時候,調(diào)用繪制
                updateBallState();
                invalidate();
            }
        });
    }

    //計算粒子數(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;
                }
                //這里的像素值,不準確
                int[] colors = new int[realWidth * realHeight];
//            @param pixels   接收位圖顏色的數(shù)組
//            @param offset   第一個要寫入像素的索引[]
//            @param stride   以像素[]為單位的項數(shù),可在其中跳過行(必須是>=位圖的寬度)??梢允秦摰?。
//            @param x        要讀取的第一個像素的x坐標
//            @param y        要讀取的第一個像素的y坐標
//            @param width    從每一行讀取的像素數(shù)
//            @param height   要讀取的行數(shù)
                mBitmap.getPixels(colors, 0, realWidth, i, j, realWidth, realHeight);
                int color = getColor(colors);
                ball.setColor(color);
                //線性增加多少,就縮小多少倍,這種來計算
                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ù) 再隨機
        return (int) (min + Math.ceil(Math.random() * (max - min)));
    }

    //動畫執(zhí)行過程中,改變粒子的狀態(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í)行動畫
            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);
        //繪制時間過長會直接崩潰的
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        //畫布的平移操作
        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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容