自定义View 粒子效果

描述

效果图:


image.png

要实现一张图片的爆炸效果,有几个关键的点:

第一点、根据图片的宽和高获取每一个像素的点,并且根据这个像素点构建在这一个像素点ball 对象;

第二点、在获取ball 的数组对象的时候,需要在子线程来做这个事情,防止UI卡顿。

第三点、启动一个动画来循环的调用绘制。

第四点、在绘制的时候需要把所有ball 都一一绘制。

创建一个粒子对象(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;//粒子运动垂直方向加速度

创建一个自定义view ,组合粒子。

 - 初始画笔Paint,bitmap对象,粒子直径(float类型)
     paint = new Paint();
     mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
 - 获取所有的粒子集合
//计算粒子数
    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   接收位图颜色的数组
//            @param offset   第一个要写入像素的索引[]
//            @param stride   以像素[]为单位的项数,可在其中跳过行(必须是>=位图的宽度)。可以是负的。
//            @param x        要读取的第一个像素的x坐标
//            @param y        要读取的第一个像素的y坐标
//            @param width    从每一行读取的像素数
//            @param height   要读取的行数
                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);
            }
        }
    }
  • 初始动画,并且监听动画变更,改变Ball的位置和invalidate();
    调用invalidate(),会执行绘制的方法,onDraw();
//        创建一个线程池,该线程池根据需要创建新线程,但是将重用以前构造的线程可用。这些池通常会提高性能
//        执行许多短期异步任务的程序。
//        对{@code execute}的调用将重用前面构造的
//        线程(如果可用)。如果没有现有线程可用,则使用一个新线程
//        线程将被创建并添加到池中。线程,
//        * 60秒内未使用的将被终止并移除
//        *缓存。因此,一个闲置足够长的池将会
//        *不消耗任何资源。注意,池与类似
//        *属性,但不同的细节(例如超时参数)
        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) {
                //动画结束
                Log.i("ball", "动画结束");
            }
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //动画每一帧的改变
                float value = (float) animation.getAnimatedValue();
//                Log.i("ball","动画value=====" +value);
                if (value == animEndValue) {
                    //动画执行结束
                    this.onAnimationFinish(animation);
                }
                //在动画改变的时候,调用绘制
                updateBallState();
                invalidate();
            }
        });
  • 循环绘制所有的粒子
 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
 * 创建时间:2019/4/26
 * 描述:优化,确认布局的宽和高度,不然会出现问题
 */
public class LZBallView extends View {
    //粒子的集合,通过获取图片的宽度或者view 的宽度来计算,子线程做这个事情哦
    private List ballList = new ArrayList<>();
    private Paint paint;//画笔

    //粒子的直径.设置默认值,后续通过属性来设置
    private float d = 20;
    //动画
    private ValueAnimator valueAnimator;
    //动画时长,后面也是通过属性配置的
    private long duration = 3000;

    private float animBeginValue = 0;
    private float animEndValue = 1;
    //图片
    private Bitmap mBitmap;
    //用于控制粒子的个数,如果是一个像素的一个像素的添加,那么粒子数目较多,默认等于
    private int ballPoor = 10;

    //是否已经开始了动画
    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);
//        创建一个线程池,该线程池根据需要创建新线程,但是将重用以前构造的线程可用。这些池通常会提高性能
//        执行许多短期异步任务的程序。
//        对{@code execute}的调用将重用前面构造的
//        线程(如果可用)。如果没有现有线程可用,则使用一个新线程
//        线程将被创建并添加到池中。线程,
//        * 60秒内未使用的将被终止并移除
//        *缓存。因此,一个闲置足够长的池将会
//        *不消耗任何资源。注意,池与类似
//        *属性,但不同的细节(例如超时参数)
        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) {
                //动画结束
                Log.i("ball", "动画结束");
            }
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //动画每一帧的改变
                float value = (float) animation.getAnimatedValue();
//                Log.i("ball","动画value=====" +value);
                if (value == animEndValue) {
                    //动画执行结束
                    this.onAnimationFinish(animation);
                }
                //在动画改变的时候,调用绘制
                updateBallState();
                invalidate();
            }
        });
    }

    //计算粒子数
    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   接收位图颜色的数组
//            @param offset   第一个要写入像素的索引[]
//            @param stride   以像素[]为单位的项数,可在其中跳过行(必须是>=位图的宽度)。可以是负的。
//            @param x        要读取的第一个像素的x坐标
//            @param y        要读取的第一个像素的y坐标
//            @param width    从每一行读取的像素数
//            @param height   要读取的行数
                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)范围内变化,取大于x的最小整数 再随机
        return (int) (min + Math.ceil(Math.random() * (max - min)));
    }

    //动画执行过程中,改变粒子的状态
    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) {
            //执行动画
            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);
    }
}

你可能感兴趣的:(自定义View 粒子效果)