自定义 View - 雪花和下雪动画

1,875 阅读3分钟

一、武汉每年都会下一场雪,然而今年却没有,雪花还是很漂亮的,据说雪花的形状有几十种,雪花图:


Paste_Image.png


大自然鬼斧神工啊。
二、前几天看到有人写了一个关于“下雪”的自定义View,很有意思,但是感觉雪花写得不是很满意,是用椭圆代替的,虽说现实中观看的确是一坨坨的,但是我还是想从细腻的角度去描绘一番(下图为代码所写)。
先上个图看效果吧:


a.gif


雪花高清图:


b.png


三、绘制过程
1.难点在于绘制当前这个雪花,我把它进行了分割,简化处理:
分为四个部分,六边形部分:


Paste_Image.png


边刺部分:


Paste_Image.png


角刺主干和角刺部分:


Paste_Image.png


2.绘制过程中将坐标原点转换至视图中心位置,以该view中心点为坐标原点,方便坐标的计算。
3.绘制正六边形:
初始化六边形边长length,先绘制最上的一条边,然后旋转画布60°,分别绘制其余5边,具体如下:

    public void drawHexagon(Canvas canvas) {
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.FILL);
        int x1 = -mLength / 2;
        int y1 = -(int) (Math.sqrt(3) * mLength / 2);
        int x2 = 0;
        int y2 = y1;
        canvas.save();
        for (int i = 0; i < 6; i++) {
            mPaint.setStrokeWidth(6);
            canvas.drawLine(x1, y1, x1 + mLength, y1, mPaint);
            mPaint.setStrokeWidth(2);
            canvas.drawLine(x2, y2, 0, 0, mPaint);
            canvas.rotate(60, 0, 0);
        }
        canvas.restore();
    }

4.绘制边刺:
该边刺为不规则图形,只能用path进行绘制,主要在于需确定好坐偏移量和上偏移量的坐标点位置,绘制一个后,同样对画布进行旋转60°,绘制其余5条:

    public void drawThorn(Canvas canvas) {
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(2);
        int y = -(int) (Math.sqrt(3) * mLength / 2);
        mHexagonPath_1.moveTo(0 - mThornPadding_1, y);
        mHexagonPath_1.lineTo(0 - mThornPadding_1 - mThornPadding_2, y - mThornHeight_1);
        mHexagonPath_1.lineTo(0, y - mThornHeight_2);
        mHexagonPath_1.lineTo(0 + mThornPadding_1 + mThornPadding_2, y - mThornHeight_1);
        mHexagonPath_1.lineTo(0 + mThornPadding_1, y);
        mHexagonPath_1.close();
        canvas.save();
        for (int i = 0; i < 6; i++) {
            canvas.drawPath(mHexagonPath_1, mPaint);
            canvas.rotate(60, 0, 0);
        }
        canvas.restore();
    }

5.绘制主干和角刺的的策略和边刺一样,只是角刺的起始位子位于垂直位置偏移30°,这个不打紧,直接想象当做垂直处理,绘制完后旋转30°,然后同上循环6次绘制其余5条,其中绘制角刺的方式(主干和边刺逻辑相似,不码了):

    public void drawCornerThorn(Canvas canvas, int h) {
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(2);
        int y = -(int) (Math.sqrt(3) * mLength / 2);
        mHexagonPath_2.moveTo(0 - mCornerThornPadding_1, y);
        mHexagonPath_2.lineTo(0 - mCornerThornPadding_1 - mCornerThornPadding_2, y - mCornerThornHeight_1);
        mHexagonPath_2.lineTo(0, y - mCornerThornHeight_2);
        mHexagonPath_2.lineTo(0 + mCornerThornPadding_1 + mCornerThornPadding_2, y - mCornerThornHeight_1);
        mHexagonPath_2.moveTo(0 + mCornerThornPadding_1, y);
        mHexagonPath_2.close();
        canvas.save();
        canvas.translate(0, -h);
        canvas.rotate(-30, 0, y);
        canvas.drawPath(mHexagonPath_2, mPaint);
        canvas.rotate(60, 0, y);
        canvas.drawPath(mHexagonPath_2, mPaint);
        canvas.restore();
    }

6.雪花的旋转采用值动画处理,动画过程改变view的旋转角度,每次重绘的时候提取到最新的旋转角度:

    /**
     * 雪花的旋转动画(属性)
     *
     * @param duration
     */
    public void snowRotateAnim(int duration) {
        if (null == mValueAnimator) {
            return;
        }
        mValueAnimator.setDuration(duration);
        mValueAnimator.setInterpolator(mLinearInterpolator);
        mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAngle = (int) mValueAnimator.getAnimatedValue();
            }
        });
        mValueAnimator.start();
    }

7.雪花向下移动:当超过边界,则重置该view的坐标位置:

    /**
     * 更新雪花的X坐标
     *
     * @param x
     */
    public void setOriginX(int x) {
        this.mOriginX += x;
        if (mOriginX > mW || mOriginX < -mWidth) {
            mOriginX = Utils.randomIntPositive(mW);
        }
    }
    /**
     * 更新雪花的Y坐标
     *
     * @param y
     */
    public void setOriginY(int y) {
        this.mOriginY += y;
        if (mOriginY > mH) {
            mOriginY = -mHeight;
        }
    }

8.遗留问题,下雪动画一次性初始化了30个雪花对象,同时在onDraw中进行绘制,略有卡顿,优化策略待定。
四、源码地址:
自定义View-雪花和下雪动画