每周一个小轮子之 仿京东加载动画

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/rikkatheworld/article/details/98884790

Demo源码

JD加载动画是这样的:
在这里插入图片描述

我做出来的是这样的:(修改前)
在这里插入图片描述
(微调后:)
在这里插入图片描述
中间的狗我不会画,所以我就画了个勾。
我把速度动画速度设置的很慢,这样的话方便观察和学习。

其实看到jd的加载动画,第一反应就是想到了 路径动画
我之前写过关于路径动画的用法:Android自定义控件开发入门与实战(6)路径动画
因为这个加载动画的路径都像是已经设好了,只要朝着指定方向绘制就行了。
难点主要在 画狗(他们用到了贝塞尔曲线,而我没有坐标,设计不出来),其他的话,其实还真没什么值得注意的地方。

所以这一篇就当做是对路径动画做一个复习吧= =。

1、观察动画顺序

从gif图,我们可以看出动画顺序为:

  1. 两个小圆向左向右移动,移动到画布的边界时消失
  2. 小圆消失时产生两个大圆,分别位于左上和右下,并且画布中间开始画狗
  3. 两个大圆开始顺时针转圈,狗的进度和大圆一样
  4. 在狗快要画完时,画布逐渐透明到消失

从上看出,我们需要:

  1. 一开始做小圆的移动动画
  2. 小圆移动完的一瞬间做大圆的转圈动画
  3. 做画勾动画
  4. 在画勾进度快结束时,做透明度动画

2、绘制路径

我们需要绘制勾的路径、大圆的路径,并用PathMeasure和路径进行联系。
我们需要在 onSizeChanged()或者画布测量完的地方开始做

 //初始化坐标参数
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //找出坐标原点,并初始化大圆半径和小圆半径
        originX = (float) (getWidth() / 2);
        originY = (float) (getHeight() / 2);
        bigRadius = (float) getWidth() / 2;
        smallRadius = bigRadius / 10;

        //画勾
        gouPath = new Path();
        gouPath.moveTo(originX - bigRadius / 2, originY);
        gouPath.lineTo(originX, originY + bigRadius / 2);
        gouPath.lineTo(originX + bigRadius / 2, originY - bigRadius / 3);

        //画大圆
        bigPath1 = new Path();
        bigPath1.moveTo(originX - bigRadius, originY);
        bigPath1.addArc(0, 0, getWidth(), getHeight(), 180, 360);

        bigPath2 = new Path();
        bigPath2.moveTo(originX + bigRadius, originY);
        bigPath2.addArc(0, 0, getWidth(), getHeight(), 0, 360);

        //画布的形状path
        canvasPath.addCircle(originX, originY, bigRadius, Path.Direction.CW);

        //连接路径动画和路径
        pathMeasureGou = new PathMeasure(gouPath, false);
        pathMeasureBigCircle1 = new PathMeasure(bigPath1, false);
        pathMeasureBigCircle2 = new PathMeasure(bigPath2, false);
    }

上面在画圆的时候我用的是Path.addArc 而不是 Path.addCircle,原因是addCircle的起点一定是x轴正方向,很显然,我们要的两个圆他们的起始位置都不同,所以不能让起点统一,而 addArc正好可以定义圆的起点。

3、懒加载定义动画

我们用ValueAnimator就能够计算出小圆移动、大圆转圈(和画勾同一个进度)、透明度变化的动画进度了
在小圆结束的时候,开始大圆转圈和画勾的动画,在画勾快要结束时,开始透明度度的动画

    //两个小圆分开的动画
    private ValueAnimator getSmallAnimation() {
        if (animatorSmallCircle == null) {
            animatorSmallCircle = ValueAnimator.ofFloat(0, 1);
            animatorSmallCircle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //获取小圆的动画进度
                    smallProgress = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            animatorSmallCircle.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    //在动画结束的时候,大圆开始转圈圈并且开始打钩,播放着两个动画
                    getGouAndBigCircleAnimation().start();
                }
            });
            animatorSmallCircle.setDuration(700);
            animatorSmallCircle.setInterpolator(new AccelerateInterpolator());
        }
        return animatorSmallCircle;
    }

    //打勾动画和大圆转圈的动画是同时进行的,所以公用一个动画
    public ValueAnimator getGouAndBigCircleAnimation() {
        if (animatorGouAndBigCircle == null) {
            animatorGouAndBigCircle = ValueAnimator.ofFloat(0, 1);
            animatorGouAndBigCircle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    bigProgress = (float) animation.getAnimatedValue();
                    //在打勾动画的进度快要结束时,设置透明度动画
                    if (bigProgress <= 0.3f) {
                        if (!getAnimatorAlpha().isStarted()) {
                            getAnimatorAlpha().start();
                        }
                    }
                    invalidate();
                }
            });
            animatorGouAndBigCircle.setDuration(1000);
            animatorGouAndBigCircle.setInterpolator(new LinearInterpolator());
        }
        return animatorGouAndBigCircle;
    }

    //透明度动画
    public ValueAnimator getAnimatorAlpha() {
        if (animatorAlpha == null) {
            //透明度从1-0,用ObjectAnimator也可以做
            animatorAlpha = ValueAnimator.ofFloat(1.0f, 0f);
            animatorAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    alphaProgress = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            animatorAlpha.setDuration(1000);
            animatorAlpha.setInterpolator(new AccelerateInterpolator());
        }
        return animatorAlpha;
    }

4、onDraw绘制

我们需要根据动画计算出的进度,来绘制这些路径:

    //就是开始各种动画
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //将画布裁剪成圆形,填充灰色
        canvas.save();
        canvas.clipPath(canvasPath);
        canvas.drawColor(Color.parseColor("#f0f0f0"));

        //小圆动画,在x轴上随着小圆动画进度而移动
        if (smallProgress <= 0.955f && smallProgress >= 0) {
            canvas.drawCircle(originX + (smallProgress * bigRadius), originY, smallRadius, smallPaint1);
            canvas.drawCircle(originX - (smallProgress * bigRadius), originY, smallRadius, smallPaint2);
        }

        //打勾动画,大圆转圈的动画,大圆是画布周长的长度是1/4
        if (bigProgress <= 1f && bigProgress >= 0) {
            float bigStop1 = pathMeasureBigCircle1.getLength() * bigProgress;
            dstBigPath1.reset();
            pathMeasureBigCircle1.getSegment(bigStop1 - (pathMeasureBigCircle1.getLength() * 0.25f), bigStop1, dstBigPath1, true);

            float bigStop2 = pathMeasureBigCircle2.getLength() * bigProgress;
            dstBigPath2.reset();
            pathMeasureBigCircle2.getSegment(bigStop1 - (pathMeasureBigCircle2.getLength() * 0.25f), bigStop2, dstBigPath2, true);

            float gouStop = pathMeasureGou.getLength() * bigProgress;
            dstGouPath.reset();
            pathMeasureGou.getSegment(0, gouStop, dstGouPath, true);
        }

        //透明度动画
        if (alphaProgress >= 0f && alphaProgress <= 1f) {
            bigPaint1.setAlpha((int) (255 * alphaProgress));
            bigPaint2.setAlpha((int) (255 * alphaProgress));
            gouPaint.setAlpha((int) (255 * alphaProgress));
        }

        //绘制路径
        canvas.drawPath(dstGouPath, gouPaint);
        canvas.drawPath(dstBigPath1, bigPaint1);
        canvas.drawPath(dstBigPath2, bigPaint2);
    }

到这里,一个简单的加载动画就做完啦

小结

  1. 路径动画不难,而且实用性特别高,效果很棒,我一开始以为jd的加载动画很炫酷所以会很复杂,但是用这个做法,其实把动画思路理清楚了,还是蛮简单的
  2. Path的addCircleaddArc有区别,addArc相比于addCircle更适合路径动画,因为它可以定义圆的起点
  3. 我这里没有用到贝塞尔曲线,因为我没有考虑到绘图坐标,以后会想办法去用一下。

后续编辑

因为那片灰色的画布一开始是没有的,所以我把他抽成一个圆出来,在做透明动画时,将这个圆也搞消失。

    canvas.drawCircle(originX,originY,canvasRadius,canvasPaint);
    ...

    //透明度动画
    if (alphaProgress >= 0f && alphaProgress <= 1f) {
            this.setAlpha(alphaProgress);
     }

猜你喜欢

转载自blog.csdn.net/rikkatheworld/article/details/98884790