Android自定义View之酷炫吊炸天的圆环

来源:互联网 发布:知乎注册 不用手机号 编辑:程序博客网 时间:2024/05/21 11:11

先看下最终的效果

静态:


动态:


在线mp4 to gif http://ezgif.com/video-to-gif

开始实现

新建一个DoughnutProgress继承View

    public class DoughnutProgress extends View {    }

先给出一些常量、变量以及公共方法的代码,方便理解后面的代码

    private static final int DEFAULT_MIN_WIDTH = 400; //View默认最小宽度    private static final int RED = 230, GREEN = 85, BLUE = 35; //基础颜色,这里是橙红色    private static final int MIN_ALPHA = 30; //最小不透明度    private static final int MAX_ALPHA = 255; //最大不透明度    private static final float doughnutRaduisPercent = 0.65f; //圆环外圆半径占View最大半径的百分比    private static final float doughnutWidthPercent = 0.12f; //圆环宽度占View最大半径的百分比    private static final float MIDDLE_WAVE_RADUIS_PERCENT = 0.9f; //第二个圆出现时,第一个圆的半径百分比    private static final float WAVE_WIDTH = 5f; //波纹圆环宽度    //圆环颜色    private static int[] doughnutColors = new int[]{            Color.argb(MAX_ALPHA, RED, GREEN, BLUE),            Color.argb(MIN_ALPHA, RED, GREEN, BLUE),            Color.argb(MIN_ALPHA, RED, GREEN, BLUE)};    private Paint paint = new Paint(); //画笔    private float width; //自定义view的宽度    private float height; //自定义view的高度    private float currentAngle = 0f; //当前旋转角度    private float raduis; //自定义view的最大半径    private float firstWaveRaduis;    private float secondWaveRaduis;    //    private void resetParams() {        width = getWidth();        height = getHeight();        raduis = Math.min(width, height)/2;    }    private void initPaint() {        paint.reset();        paint.setAntiAlias(true);    }

重写onMeasure方法,为什么要重写onMeasure方法可以看我的上一篇文章,点这里

    /**     * 当布局为wrap_content时设置默认长宽     *     * @param widthMeasureSpec     * @param heightMeasureSpec     */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));    }    private int measure(int origin) {        int result = DEFAULT_MIN_WIDTH;        int specMode = MeasureSpec.getMode(origin);        int specSize = MeasureSpec.getSize(origin);        if (specMode == MeasureSpec.EXACTLY) {            result = specSize;        } else {            if (specMode == MeasureSpec.AT_MOST) {                result = Math.min(result, specSize);            }        }        return result;    }

下面就是最重要的重写onDraw方法,大致流程如下

在开始绘制之前,先初始化widthheightraduis, 以及将View的中心作为原点

    resetParams();    //将画布中心设为原点(0,0), 方便后面计算坐标    canvas.translate(width / 2, height / 2);

实现静态的渐变圆环

  • 画渐变圆环

          float doughnutWidth = raduis * doughnutWidthPercent;//圆环宽度      //圆环外接矩形      RectF rectF = new RectF(      -raduis * doughnutRaduisPercent,       -raduis * doughnutRaduisPercent,       raduis * doughnutRaduisPercent,       raduis * doughnutRaduisPercent);      initPaint();      paint.setStrokeWidth(doughnutWidth);      paint.setStyle(Paint.Style.STROKE);      paint.setShader(new SweepGradient(0, 0, doughnutColors, null));      canvas.drawArc(rectF, 0, 360, false, paint);

    通过修改doughnutColors可以实现不同的渐变效果

  • 画圆环旋转头部的圆

          //画旋转头部圆      initPaint();      paint.setStyle(Paint.Style.FILL);      paint.setColor(Color.argb(MAX_ALPHA, RED, GREEN, BLUE));      canvas.drawCircle(raduis * doughnutRaduisPercent, 0, doughnutWidth / 2, paint);

此时运行代码得到效果如下图:


我们还可以在绘制圆环之前通过旋转画布得到不同初始状态

    canvas.rotate(-45, 0, 0);

    canvas.rotate(-180, 0, 0);

此时聪明的你应该已经想到怎么让这个圆环旋转起来了吧^_^

对!正如你所想的,就是通过canvas.rotate方法不停地旋转画布(这个“地”是这么用的吧o(╯□╰)o)

让圆环旋转起来

在绘制圆环之前加上下面的代码:

    //转起来    canvas.rotate(-currentAngle, 0, 0);    if (currentAngle >= 360f){        currentAngle = currentAngle - 360f;    } else{        currentAngle = currentAngle + 2f;    }

然后再让一个线程循环刷新就好了

private Thread thread = new Thread(){    @Override    public void run() {        while(true){            try {                Thread.sleep(10);            } catch (InterruptedException e) {                e.printStackTrace();            }            postInvalidate();        }    }};

试试!转起来了吗O(∩_∩)O~

下面是比较有意思的部分,实现类似水波涟漪的效果

分析水波涟漪效果的实现原理(画了张草图方便理解):


假设淡黄色背景区域为整个View的大小

黑色圆圈为View内的最大圆(半径为R3)

橙色圆环代表渐变圆环

红色圆圈代表圆环的外圆(半径为R1)

紫色圆圈是干啥子的,待会儿再介绍~(半径为R2)

通过观察实现的最终效果,可以发现有个圆的半径从R1逐渐增大R3,不透明度逐渐减小到0。

那是不是这样周而复始就可以实现最终的效果了呢?

没那么简单。。。

仔细观察发现,第二个圆不是等到第一个圆的半径增大到R3才开始出现的,而是在将要消失的时候就出现了,有一段时间是两个圆同时存在的。

那么我们就假设当第一个圆的半径增大到R2,第二个圆开始出现。

开始想象两个圆的循环运行模型~~~

我的方案是:

绘制两个圆,每个圆的半径都从R1增大到R1+2x(R2-R1),不透明度还是从R1到R3的过程中逐渐变为0,也就是当圆的半径大于R3时,不透明度就为0了(不可见了),将第一个圆半径初始值设为R1,第二个圆半径初始值设为R2。这样两个圆半径同时逐渐增大,当半径大于 R1+2x(R2-R1)时又重新回到R1大小继续增大,就实现了类似水波涟漪的效果了。

    //实现类似水波涟漪效果    initPaint();    paint.setStyle(Paint.Style.STROKE);    paint.setStrokeWidth(5);    secondWaveRaduis = calculateWaveRaduis(secondWaveRaduis);    firstWaveRaduis = calculateWaveRaduis(secondWaveRaduis + raduis*(MIDDLE_WAVE_RADUIS_PERCENT - doughnutRaduisPercent) - raduis*doughnutWidthPercent/2);    paint.setColor(Color.argb(calculateWaveAlpha(secondWaveRaduis), RED, GREEN, BLUE));    canvas.drawCircle(0, 0, secondWaveRaduis, paint); //画第二个圆(初始半径较小的)    initPaint();    paint.setStyle(Paint.Style.STROKE);    paint.setStrokeWidth(5);    paint.setColor(Color.argb(calculateWaveAlpha(firstWaveRaduis), RED, GREEN, BLUE));    canvas.drawCircle(0, 0, firstWaveRaduis, paint); //画第一个圆(初始半径较大的)    /**     * 计算波纹圆的半径     * @param waveRaduis     * @return     */    private float calculateWaveRaduis(float waveRaduis){        if(waveRaduis < raduis*doughnutRaduisPercent + raduis*doughnutWidthPercent/2){            waveRaduis = raduis*doughnutRaduisPercent + raduis*doughnutWidthPercent/2;        }        if(waveRaduis > raduis*MIDDLE_WAVE_RADUIS_PERCENT + raduis*(MIDDLE_WAVE_RADUIS_PERCENT - doughnutRaduisPercent) - raduis*doughnutWidthPercent/2){            waveRaduis = waveRaduis - (raduis*MIDDLE_WAVE_RADUIS_PERCENT + raduis*(MIDDLE_WAVE_RADUIS_PERCENT - doughnutRaduisPercent) - raduis*doughnutWidthPercent/2) + raduis*doughnutWidthPercent/2 + raduis*doughnutRaduisPercent;        }            waveRaduis += 0.6f;        return waveRaduis;    }    /**     * 根据波纹圆的半径计算不透明度     * @param waveRaduis     * @return     */    private int calculateWaveAlpha(float waveRaduis){        float percent = (waveRaduis-raduis*doughnutRaduisPercent-raduis*doughnutWidthPercent/2)/(raduis-raduis*doughnutRaduisPercent-raduis*doughnutWidthPercent/2);        if(percent >= 1f){            return 0;        }else{            return (int) (MIN_ALPHA*(1f-percent));        }    }

全部测试代码下载地址:

https://github.com/hellsam/DoughnutProgressDemo

欢迎留言交流,如有描述不当或错误的地方还请留言告知



作者:hellsam
链接:http://www.jianshu.com/p/6c5e4dce11aa
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。