美文网首页自定义控件
自定义View 粒子效果

自定义View 粒子效果

作者: 占卜L | 来源:发表于2019-04-26 18:29 被阅读20次

    描述

    效果图:


    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<Ball> 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 粒子效果

        本文链接:https://www.haomeiwen.com/subject/ktiqnqtx.html