Android自定義View之酷炫吊炸天的圓環(huán)(二)

先看下最終的效果

靜態(tài):

動(dòng)態(tài):

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

開始實(shí)現(xiàn)

新建一個(gè)DoughnutProgress繼承View

    public class DoughnutProgress extends View {
    
    }

先給出一些常量、變量以及公共方法的代碼,方便理解后面的代碼

    private static final int DEFAULT_MIN_WIDTH = 400; //View默認(rèn)最小寬度
    private static final int RED = 230, GREEN = 85, BLUE = 35; //基礎(chǔ)顏色,這里是橙紅色
    private static final int MIN_ALPHA = 30; //最小不透明度
    private static final int MAX_ALPHA = 255; //最大不透明度
    private static final float doughnutRaduisPercent = 0.65f; //圓環(huán)外圓半徑占View最大半徑的百分比
    private static final float doughnutWidthPercent = 0.12f; //圓環(huán)寬度占View最大半徑的百分比
    private static final float MIDDLE_WAVE_RADUIS_PERCENT = 0.9f; //第二個(gè)圓出現(xiàn)時(shí),第一個(gè)圓的半徑百分比
    private static final float WAVE_WIDTH = 5f; //波紋圓環(huán)寬度

    //圓環(huán)顏色
    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; //當(dāng)前旋轉(zhuǎn)角度
    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方法可以看我的上一篇文章,點(diǎn)這里

    /**
     * 當(dāng)布局為wrap_content時(shí)設(shè)置默認(rèn)長(zhǎng)寬
     *
     * @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方法,大致流程如下

在開始繪制之前,先初始化width、height、raduis, 以及將View的中心作為原點(diǎn)

    resetParams();

    //將畫布中心設(shè)為原點(diǎn)(0,0), 方便后面計(jì)算坐標(biāo)
    canvas.translate(width / 2, height / 2);

實(shí)現(xiàn)靜態(tài)的漸變圓環(huán)

  • 畫漸變圓環(huán)

        float doughnutWidth = raduis * doughnutWidthPercent;//圓環(huán)寬度
        //圓環(huán)外接矩形
        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);
    

通過(guò)修改doughnutColors可以實(shí)現(xiàn)不同的漸變效果

  • 畫圓環(huán)旋轉(zhuǎn)頭部的圓

        //畫旋轉(zhuǎn)頭部圓
        initPaint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.argb(MAX_ALPHA, RED, GREEN, BLUE));
        canvas.drawCircle(raduis * doughnutRaduisPercent, 0, doughnutWidth / 2, paint);
    

此時(shí)運(yùn)行代碼得到效果如下圖:

我們還可以在繪制圓環(huán)之前通過(guò)旋轉(zhuǎn)畫布得到不同初始狀態(tài)

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

此時(shí)聰明的你應(yīng)該已經(jīng)想到怎么讓這個(gè)圓環(huán)旋轉(zhuǎn)起來(lái)了吧_

對(duì)!正如你所想的,就是通過(guò)canvas.rotate方法不停地旋轉(zhuǎn)畫布(這個(gè)“地”是這么用的吧o(╯□╰)o)

讓圓環(huán)旋轉(zhuǎn)起來(lái)

在繪制圓環(huán)之前加上下面的代碼:

    //轉(zhuǎn)起來(lái)
    canvas.rotate(-currentAngle, 0, 0);
    if (currentAngle >= 360f){
        currentAngle = currentAngle - 360f;
    } else{
        currentAngle = currentAngle + 2f;
    }

然后再讓一個(gè)線程循環(huán)刷新就好了

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

試試!轉(zhuǎn)起來(lái)了嗎O(∩_∩)O~

下面是比較有意思的部分,實(shí)現(xiàn)類似水波漣漪的效果

分析水波漣漪效果的實(shí)現(xiàn)原理(畫了張草圖方便理解):

假設(shè)淡黃色背景區(qū)域?yàn)檎麄€(gè)View的大小

黑色圓圈為View內(nèi)的最大圓(半徑為R3)

橙色圓環(huán)代表漸變圓環(huán)

紅色圓圈代表圓環(huán)的外圓(半徑為R1)

紫色圓圈是干啥子的,待會(huì)兒再介紹~(半徑為R2)

通過(guò)觀察實(shí)現(xiàn)的最終效果,可以發(fā)現(xiàn)有個(gè)圓的半徑從R1逐漸增大R3,不透明度逐漸減小到0。

那是不是這樣周而復(fù)始就可以實(shí)現(xiàn)最終的效果了呢?

沒(méi)那么簡(jiǎn)單。。。

仔細(xì)觀察發(fā)現(xiàn),第二個(gè)圓不是等到第一個(gè)圓的半徑增大到R3才開始出現(xiàn)的,而是在將要消失的時(shí)候就出現(xiàn)了,有一段時(shí)間是兩個(gè)圓同時(shí)存在的。

那么我們就假設(shè)當(dāng)?shù)谝粋€(gè)圓的半徑增大到R2,第二個(gè)圓開始出現(xiàn)。

開始想象兩個(gè)圓的循環(huán)運(yùn)行模型~~~

我的方案是:

繪制兩個(gè)圓,每個(gè)圓的半徑都從R1增大到R1+2x(R2-R1),不透明度還是從R1到R3的過(guò)程中逐漸變?yōu)?,也就是當(dāng)圓的半徑大于R3時(shí),不透明度就為0了(不可見了),將第一個(gè)圓半徑初始值設(shè)為R1,第二個(gè)圓半徑初始值設(shè)為R2。這樣兩個(gè)圓半徑同時(shí)逐漸增大,當(dāng)半徑大于 R1+2x(R2-R1)時(shí)又重新回到R1大小繼續(xù)增大,就實(shí)現(xiàn)了類似水波漣漪的效果了。

    //實(shí)現(xiàn)類似水波漣漪效果
    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); //畫第二個(gè)圓(初始半徑較小的)

    initPaint();
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(5);
    paint.setColor(Color.argb(calculateWaveAlpha(firstWaveRaduis), RED, GREEN, BLUE));
    canvas.drawCircle(0, 0, firstWaveRaduis, paint); //畫第一個(gè)圓(初始半徑較大的)
    
    
    /**
     * 計(jì)算波紋圓的半徑
     * @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;
    }

    /**
     * 根據(jù)波紋圓的半徑計(jì)算不透明度
     * @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));
        }
    }

全部測(cè)試代碼下載地址:

https://github.com/hellsam/DoughnutProgressDemo

<strong>歡迎留言交流,如有描述不當(dāng)或錯(cuò)誤的地方還請(qǐng)留言告知</strong>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容