WaveProgressBar -- 波浪进度条

前言

很久之前,在简书上看到一个圆形双曲线波浪的进度条的文章,感觉很不错,结果,看一下代码,却是IOS的,嗯,其实github上也有很多android的双曲线动画,不过,我看了几个控件的源码(这个有空在分析),看不懂~原谅我把数学扔给体育老师了,里面涉及到 反余弦函数,还有Matrix的使用,好吧,这个我是真的不是很懂,然后,决定自己写一个,效果:

这里写图片描述

在文章的结尾,我会贴上代码地址。事实上,我写这篇文章的主要目的,更多是希望能描述清楚这个控件的使用,以及这个控件如何画出来。有过真实开发的同学都知道,需求千奇百怪,你永远不会知道产品下一个改动是什么。


准备

事实上,我并不准备重复我以前写过的东西,这里我只会简述一下几个API的使用:

path.quadTo()  参数为绝对位置
path.rQuadTo() 参数为相对前一个点的相对位置

上面这两个API如果不是很明白的话,可以看下我以前的一篇文章 Android 双曲线波浪动画(第一发),这篇文章里面你可以看到我很详细的讲述了这两个API的不同,当然,你还可以欣赏到我灵魂画师般的画技^-^。

path.arcTo  添加一个圆弧到path,如果圆弧的起点和上次最后一个坐标点不相同,就连接两个点

上面的解释感觉好抽象,举个实际简单的列子:
这里写图片描述
假设这是一个左上角是原点的矩形,x轴边长 = 20, y轴边长 = 10,那么如图,我想要填充如图的形状,那么问题主要就是怎么连接 B – C。
伪代码:

path.moveTo(0, 0);  从 00 出发
path.lineTo(5, 0);  A 连接到 C
Rect rect = new Rect(0, 0, 20, 10); 整个矩形的Rect对象
path.arcTo(rect, 180, 90, true); 180 代表着开始的角度,90代表着需要绘制的角度, true的意思就是:将最后一个点移动到圆弧起点,即不连接最后一个点与圆弧起点,你可以理解为只绘制路径,不填充圆弧,填充圆弧的话,就会变成三角的形状了

至此,如果你确实理解了以上的方法,那么接下来就是一些细节了,如果还是不太明白,那么接下来的篇幅,希望能帮助你理解。


代码思路

秉承着本人的习惯,代码就是思路,并且,最清楚的方法莫过于 onDraw。

onDraw :

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(cavans_bg);

        if (progress > 0 && progress < max)
            drawWave(canvas);
        else if (progress == max)
            canvas.drawColor(front_wave_color);

        drawText(canvas);

        if (shape.equals(CIRCLE))
            drawArcs(canvas);

    }

整个绘制,我分为了三部分: drawWave ( 绘制动态波浪 ) , drawText(绘制文字), drawArcs(绘制四角的伪三角形)。

先来看看简单的部分,drawText 和 drawArcs

drawText:很简单的代码,默认情况下绘制在控件中心

// 默认情况下的值,side_lenght 是正方形的边长。
 if (text_margin_top == 0)
            text_margin_top = (int) (side_length / 2 + (fontMetrics.descent - fontMetrics.ascent) / 2);

protected void drawText(Canvas canvas) {
        if (text_follow_progress)
            text = progress + PERCENT_CHAR;
        int textLength = (int) textPaint.measureText(text);
        int i = (side_length - textLength) / 2;
        canvas.drawText(text, i, text_margin_top, textPaint);

    }

drawArcs: 其实就是以 左上,右上,右下,左下 的顺序绘制了四个伪三角形。

    protected void drawArcs(Canvas canvas) {
        pathPaint.setColor(arcColor);

        path.reset();
        path.moveTo(half_side_length, 0);
        path.arcTo(rectf, 180, 90, true);
        path.lineTo(0, 0);
        path.close();
        canvas.drawPath(path, pathPaint);

        path.reset();
        path.moveTo(half_side_length, 0);
        path.arcTo(rectf, 270, 90, true);
        path.lineTo(side_length, 0);
        path.close();
        canvas.drawPath(path, pathPaint);

        path.reset();
        path.moveTo(side_length, 0);
        path.arcTo(rectf, 0, 90, true);
        path.lineTo(side_length, side_length);
        path.close();
        canvas.drawPath(path, pathPaint);

        path.reset();
        path.moveTo(half_side_length, side_length);
        path.arcTo(rectf, 90, 90, true);
        path.lineTo(0, side_length);
        path.close();
        canvas.drawPath(path, pathPaint);
    }

在看完简单的代码后,就可以来看看主要的代码 drawWave,对了,在看 drawWave 之前,需要看下一些参数的含义,以及参数的计算,我将这些步骤大都放在 onSizeChanged 方法中,也就是当 onMeasure 方法完全调用完成,完成了对 View 宽高的测量之后,再来计算我需要的值。

onSizeChanged

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
//        Log.d(TAG, "w = " + w + " : h = " + h);
        // 整个控件的矩形区间对象
        rectf = new RectF(0, 0, side_length, side_length);

        // side_length 其实就是矩形的边长,因为这个矩形是个正方形
        // 下面三行代码分别计算了 二分之一、四分之一、以及 八分之一 的边长值
        half_side_length = side_length / 2;
        quarter_side_length = half_side_length / 2;
        eighth_side_length = quarter_side_length / 2;

        //计算每一个进度的高度
        percent_height = side_length / max;

        // 默认情况下,波浪的振幅
        if (dwave == -1)
            dwave = side_length / 40 * 3;

        if (text_margin_top == 0)
            text_margin_top = (int) (side_length / 2 + (fontMetrics.descent - fontMetrics.ascent) / 2);

        initPaints();

    }

drawWave:注释会详细的写在代码里

 protected void drawWave(Canvas canvas) {
        //计算当前进度的高度
        int wave_height = side_length - progress * percent_height;

        // baseX 是波浪起始点的 X 轴坐标,其中 dx 是用来控制变化起始点的位置的
        // 之后会贴出 dx 的变化代码
        int baseX = -side_length + dx;


        if (baseX > 0)
            baseX = 0;

        //先绘制底部的波浪
        path.reset();
        pathPaint.setColor(behind_wave_color);
        path.moveTo(baseX, wave_height);
        for (int i = 0; i < 2; i++) {
            path.rQuadTo(quarter_side_length, -dwave, half_side_length, 0);
            path.rQuadTo(quarter_side_length, dwave, half_side_length, 0);
        }
        path.lineTo(side_length, side_length);
        path.lineTo(0, side_length);
        path.close();
        canvas.drawPath(path, pathPaint);

        //再绘制顶部的波浪
        // 注意:这里我默认写死了起始点的 X 坐标比 底部波浪的起始坐标 向左偏离 八分之一的宽度,这样两层波浪的效果才会逼真一点,同步的话会感觉很怪异
        path.reset();
        pathPaint.setColor(front_wave_color);
        path.moveTo(baseX - eighth_side_length, wave_height);
        for (int i = 0; i < 3; i++) {
            path.rQuadTo(quarter_side_length, dwave, half_side_length, 0);
            path.rQuadTo(quarter_side_length, -dwave, half_side_length, 0);
        }
        path.lineTo(side_length, side_length);
        path.lineTo(0, side_length);
        path.close();
        canvas.drawPath(path, pathPaint);
    }

dx 的变化代码:一个简单的属性动画

  public void startWaveAnimation() {
        animation = true;
        int value = 0;
        if (side_length == 0)
            value = Math.min(width, height);
        else if (height == width && height == 0)
            value = side_length;
        else
            value = Math.min(side_length, Math.min(width, height));
        valueAnimator = ValueAnimator.ofInt(0, value);
        valueAnimator.setDuration(wave_duration);
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                dx = (int) valueAnimator.getAnimatedValue();
                if (animation)
                    postInvalidate();
            }
        });
        valueAnimator.start();
    }

总结

到这里其实整个控件就没什么好说的了,其他的只是一些零碎的细节,以及bug 的修复代码。其实本周计划是出两篇博客的,一篇是自己写的控件,就是本篇,另一篇是对 github 上开源控件的源码解读,然而,就像开篇所述,反三角函数 和 Matrix 把我拦住了,不过,一定会写的,等我把数学捡起来,给自己定个小目标:下星期完成。

源码地址

WaveProgressBar

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值