1. 程式人生 > >自定義View,貝賽爾曲線實現水波紋進度條

自定義View,貝賽爾曲線實現水波紋進度條

最終的效果:

 思路就是在onDraw()中畫一些內容,主要方法有這些:

/**
 * 剪裁圓形區域
 */
clipCircle(canvas);
/**
 * 畫圓邊線
 */
drawCircle(canvas);
/**
 * 畫波浪線
 */
drawWave(canvas);
/**
 * 畫進度文字
 */
drawText(canvas);

1.clipCircle(canvas)這個方法剪裁出一個圓形畫布:

private void clipCircle(Canvas canvas) {
    Path circlePath = new Path();
    circlePath.addCircle(screenWidth / 2, screenHeignt / 2, screenHeignt / 2, Path.Direction.CW);
    canvas.clipPath(circlePath);
}

2.drawCircle(canvas)畫出圓的邊線:

private void drawCircle(Canvas canvas) {
        canvas.drawCircle(screenHeignt / 2, screenHeignt / 2, screenHeignt / 2, circlePaint);
    }

3.drawWave(canvas)畫波浪線:

 private void drawWave(Canvas canvas) {
        int height = (int) (progress / 100 * screenHeignt);
        startPoint.y = -height;
        canvas.translate(0, screenHeignt);
        path = new Path();
        wavePaint.setStyle(Paint.Style.FILL);
        wavePaint.setColor(Color.GREEN);
        int wave = screenWidth / 4;
        path.moveTo(startPoint.x, startPoint.y);
        for (int i = 0; i < 4; i++) {
            int startX = startPoint.x + i * wave * 2;
            int endX = startX + 2 * wave;
            if (i % 2 == 0) {
                path.quadTo((startX + endX) / 2, startPoint.y + amplitude, endX, startPoint.y);
            } else {
                path.quadTo((startX + endX) / 2, startPoint.y - amplitude, endX, startPoint.y);
            }
        }
        path.lineTo(screenWidth, screenHeignt / 2);
        path.lineTo(-screenWidth, screenHeignt / 2);
        path.lineTo(-screenWidth, 0);
        path.close();
        canvas.drawPath(path, wavePaint);
        startPoint.x += 10;
        if (startPoint.x > 0) {
            startPoint.x = -screenWidth;
        }
        path.reset();
    }

這段程式碼說來有些複雜;

首先我們看到height,這個是波浪曲線所在的y軸的曲線,progress是傳進來的進度為了計算我轉化成百分比先除以100再乘以螢幕高度(這裡的螢幕高度大家可以理解成直徑)。

其次我把螢幕y軸移動到了控制元件的底部也就是如圖這樣的:

接下來我就開始建立path設定畫筆開始畫波浪線了, 我們設定的startpoint為x軸負一個螢幕的寬度,從這個點開始畫兩個完整的正弦曲線,wave的大小為screenWidth/4(在圖中標註了),在for中改變startpoint並且畫出曲線(每個人可能有不一樣的實現方法,這段程式碼看起來可能費勁),最終把這個波浪與右下做下點連起來形成閉合圖形,用於著色,那麼畫完了怎麼讓波浪凍起來呢?我們每次重新整理ondraw()都讓起始點的x+10,然後判斷如果x大於0,也就是起始點來到了(0,screenWidth)這個點的時候,我們把startpoint的x座標回到最初的位置即可,這樣每次重新整理就形成了一個動態波浪的效果了。波浪的高度通過剛開始的startPoint.y = -height來實現,加符號因為y軸複方向為進度的增長方向。

4.drawText()繪製文字:

private void drawText(Canvas canvas) {
        Rect targetRect = new Rect(0, -screenHeignt, screenWidth, 0);
        Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
        int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
        textPaint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(String.valueOf((int) textProgress + "%"), targetRect.centerX(), baseline, textPaint);
    }

 繪製文字為了讓文字居中我做了一些必要的計算

 

PS:細心的小夥伴在setProgress()這個方法中發現了一點問題為什麼會這麼寫呢:

public void setProgress(float progress) {
        this.textProgress = progress;
        if (progress == 100) {
            this.progress = progress + amplitude;
        } else {
            this.progress = progress;
        }
    }

原因是因為當我們的進度達到100%的時候,由於sin曲線和從左到右的波動還是會出現空白流動的區域,就像如圖這樣:

這樣豈不是很蛋疼,進度滿了不能出現這種情況,所以我手動判斷如果進度在100%就把y座標加上一個保險值,保證不出現以上這個情況。 

 

書寫本文旨在練習與理解自定義view、貝賽爾曲線等等知識,如有錯誤歡迎指正,原始碼連結GitHub傳送門,歡迎clone&start