Android自定义View实现飘雪效果

无聊又来写自定义View了,2020南京一直不下雪,那就自己写一个,先上图
Android自定义View实现飘雪效果_第1张图片
直接上代码

public class SnowView extends View {
    private Paint snowPaint;
    private Random random = new Random();
    private List<Snow> snows = new ArrayList<>();
    private Handler handler = new Handler();

    public SnowView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        snowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        snowPaint.setColor(Color.WHITE);
        snowPaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        snows.clear();
        for (int i = 0; i < 100; i++) {
            snows.add(genASnow());
        }
    }

    /**
     * 生成一朵雪花
     *
     * @return
     */
    private Snow genASnow() {
        Snow snow = new Snow();
        snow.setDegree(random.nextInt(60));
        int radius = 10 + random.nextInt(10);//随机半径(雪花的大小)
        snow.setRadius(radius);
        snow.setWidth((int) (radius * 0.2));//雪花的粗细根据半径来计算
        snow.setSpeed((int) (2 + Math.abs((radius - 20)) * 0.5));//雪花的速度:基础速度+随机值(根据雪花大小决定)

        int red = random.nextInt(255);//随机一个颜色
        int green = random.nextInt(255);
        int blue = random.nextInt(255);
        snow.setColor(Color.rgb(red, green, blue));

        Point startPoint = new Point(random.nextInt(getWidth()), -random.nextInt(getHeight()));//雪花的起始点,也是第1个贝塞尔控制点
        snow.setPointStart(startPoint);
        snow.setPointControl(new Point(random.nextInt(getWidth()), random.nextInt(getHeight())));//雪花的起始点,也是第2个贝塞尔控制点
        snow.setPointEnd(new Point(random.nextInt(getWidth()), getHeight() + random.nextInt(getHeight())));//雪花的起始点,也是第3个贝塞尔控制点
        snow.setX(startPoint.x);
        snow.setY(startPoint.y);
        return snow;
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.BLACK);
        //先绘制
        for (int i = 0; i < snows.size(); i++) {
            Snow snow = snows.get(i);
            drawASnow(canvas, snow);
        }
        //进行Y轴的变换和角度的变换
        for (int i = 0; i < snows.size(); i++) {
            Snow snow = snows.get(i);
            int y = snow.getY();
            int d = snow.getDegree();
            d = d + 1;
            y += snow.getSpeed();
            if (y >= snow.getPointEnd().y) {//当Y值在屏幕下方的时候重置到上方
                y = -random.nextInt(getHeight());
            }
            snow.setY(y);
            snow.setDegree(d);
        }
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                invalidate();
            }
        }, 10);
    }

    private void drawASnow(Canvas canvas, Snow snow) {
        int snowX = snow.getX();
        int snowY = snow.getY();
        int snowRadius = snow.getRadius();
        canvas.save();
        snowPaint.setColor(snow.getColor());
        snowPaint.setStrokeWidth(snow.getWidth());
        canvas.rotate(snow.getDegree(), snowX, snowY);

        for (int i = 0; i < 6; i++) {
            //通过旋转的方式绘制雪花的6条边
            int lineStartX = snowX;
            int lineEndX = snowX + snowRadius;
            int lineStartY = snowY;
            int lineEndY = snowY;

            //开始绘制雪花的分杈,分杈有两个与边的角度是60度,上边一个下边一个,分叉点的比例设置为0.6,分叉的长度设置为雪花半径的0.4
            int line1StartX = (int) (lineStartX + snowRadius * 0.6);
            double degree60R = Math.toRadians(60);
            int line1EndX = (int) (line1StartX + Math.cos(degree60R) * snowRadius * 0.4);
            int line1StartY = lineStartY;
            int line1EndY = (int) (line1StartY - Math.sin(degree60R) * snowRadius * 0.4);

            int line2StartX = (int) (lineStartX + snowRadius * 0.6);
            int line2EndX = (int) (line1StartX + Math.cos(degree60R) * snowRadius * 0.4);
            int line2StartY = lineStartY;
            int line2EndY = (int) (line1StartY + Math.sin(degree60R) * snowRadius * 0.4);

            //其实一朵雪花就是简单的直线绘制起来的
            canvas.rotate(60, snowX, snowY);
            canvas.drawLine(lineStartX, lineStartY, lineEndX, lineEndY, snowPaint);
            canvas.drawLine(line1StartX, line1StartY, line1EndX, line1EndY, snowPaint);
            canvas.drawLine(line2StartX, line2StartY, line2EndX, line2EndY, snowPaint);
        }
        canvas.restore();
    }

    private static class Snow {
        private int width;
        private int x;
        private int y;
        private int degree;
        private int color;
        private int radius;
        private int speed;

        //加入控制点的目的是为了实现贝塞尔路径的效果,因为雪花从天上飘下来肯定不会只直直的下来的,加上曲线路径会逼真一些
        private Point pointStart;
        private Point pointControl;
        private Point pointEnd;

        public Point getPointStart() {
            return pointStart;
        }

        public void setPointStart(Point pointStart) {
            this.pointStart = pointStart;
        }

        public Point getPointControl() {
            return pointControl;
        }

        public void setPointControl(Point pointControl) {
            this.pointControl = pointControl;
        }

        public Point getPointEnd() {
            return pointEnd;
        }

        public void setPointEnd(Point pointEnd) {
            this.pointEnd = pointEnd;
        }


        public int getSpeed() {
            return speed;
        }

        public void setSpeed(int speed) {
            this.speed = speed;
        }

        public int getRadius() {
            return radius;
        }

        public void setRadius(int radius) {
            this.radius = radius;
        }

        public int getWidth() {
            return width;
        }

        public void setWidth(int width) {
            this.width = width;
        }

        public int getX() {
            float tick = y * 1.0f / (pointEnd.y - pointStart.y);
            return BezierUtil.CalculateBezierPointForQuadratic(tick, pointStart, pointControl, pointEnd).x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }

        public int getDegree() {
            return degree;
        }

        public void setDegree(int degree) {
            this.degree = degree;
        }

        public int getColor() {
            return color;
        }

        public void setColor(int color) {
            this.color = color;
        }
    }

    public static class BezierUtil {
        /**
         * B(t) = (1 - t)^2 * P0 + 2t * (1 - t) * P1 + t^2 * P2, t ∈ [0,1]
         *
         * @param t  曲线长度比例
         * @param p0 起始点
         * @param p1 控制点
         * @param p2 终止点
         * @return t对应的点
         */
        public static Point CalculateBezierPointForQuadratic(float t, Point p0, Point p1, Point p2) {
            Point point = new Point();
            float temp = 1 - t;
            point.x = (int) (temp * temp * p0.x + 2 * t * temp * p1.x + t * t * p2.x);
            point.y = (int) (temp * temp * p0.y + 2 * t * temp * p1.y + t * t * p2.y);
            return point;
        }


        /**
         * B(t) = P0 * (1-t)^3 + 3 * P1 * t * (1-t)^2 + 3 * P2 * t^2 * (1-t) + P3 * t^3, t ∈ [0,1]
         *
         * @param t  曲线长度比例
         * @param p0 起始点
         * @param p1 控制点1
         * @param p2 控制点2
         * @param p3 终止点
         * @return t对应的点
         */
        public static Point CalculateBezierPointForCubic(float t, Point p0, Point p1, Point p2, Point p3) {
            Point point = new Point();
            float temp = 1 - t;
            point.x = (int) (p0.x * temp * temp * temp + 3 * p1.x * t * temp * temp + 3 * p2.x * t * t * temp + p3.x * t * t * t);
            point.y = (int) (p0.y * temp * temp * temp + 3 * p1.y * t * temp * temp + 3 * p2.y * t * t * temp + p3.y * t * t * t);
            return point;
        }
    }

}

你可能感兴趣的:(android)