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

先看下最终的效果

静态:

动态:

在线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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容