android 自定義酷炫進度條動畫

小洲小舟發表於2019-01-27

源起

  • 公司專案用到一個進度條動畫,需要自己繪製從0載入到100的情況,而且效果需要很酷炫。最後是採用了lottie動畫+一部分自定義View來實現,拆分了出來。過後,覺得其實自己實現這樣的一個效果也不難,便開始了以下的嘗試。先看下效果

android 自定義酷炫進度條動畫

分析

  1. 分析下:最外層是一個由小圓點順時針旋轉的圓、第二層是直接一個圓、第三層有兩層:內層是由矩形逆時針旋轉的圓,外層是一個圓環、最後是文字展示。
  2. 實現思路是這樣:分成多個View來實現,第一層圓點是一個View,繪製完成是使用ObjectAnimator進行旋轉;第二層和第三層的外層和文字是一個View,第三層內層矩形是一個View,同樣是繪製完成使用ObjectAnimator旋轉。(這裡為何分成三個View咧,因為涉及到旋轉動畫,我想實現旋轉的時候,是對畫布canvas物件進行旋轉,而在一個View裡只有一個畫布canvas物件,如果所有都繪製在一個canvas上,則旋轉的時候就都會旋轉,實現不了我要的效果。如果讀者有更好的解決方案歡迎提出)

android 自定義酷炫進度條動畫

開幹

第一部分

  1. 進度條動畫 感謝這篇部落格作者給我提供了很好的思路,讓我舉一反三,如果讀者第一部分看的很模糊的話,可以結合這篇部落格享用
  2. 第一層圓點和第三層內層矩形應該怎麼繪製?這裡實現大同小異放在一起說

android 自定義酷炫進度條動畫

  1. 看到此圖不用望而生畏,其實很簡單,我們最終在畫布上繪製的還是一個一個的圓,藍色圓是真正要繪製的,空白圓是間隔距離,不需要繪製出來。現在總共是100個藍色圓,100個空心圓,每個圓佔據的角度是360/200=1.8度;繪製完藍色圓以後,呼叫canvas.ratate()將畫布旋轉3.6度到下一個藍色圓圓心位置開始繪製圓,最終就有100個藍色圓了。
  2. 怎麼繪製藍色圓?需要確定藍色圓的圓心位置和半徑。我們可以在onMeasure()中拿到View的width和height,除以2得到就是圓心座標(centerX,centerY),R0就是寬或者高/2可以得到的半徑,我們要求出r:

R1 + r = R0
R1 * sin0.9° = r
由以上兩式可以得出:r = (R0*sin0.9°)/(1+sin0.9°)——R0已知
小藍圓的圓心為:(centerX,centerY-R0+r)

  1. 這樣就可以繪製出來了,這裡把sin0.9°提高到sin1°,讓藍色圓大一點。
    關鍵程式碼如下:(為了美觀,我將100個藍色小圓改成了50個,而且每次旋轉3.6*2=7.2度,也就是隔開了三個白色小圓的距離,繪製50次,每次轉過了7.2度,50 * 7.2=360度,每次都繪製一個藍色圓,最後是50個小藍圓)

android 自定義酷炫進度條動畫

@Override
    protected void onDraw(Canvas canvas) {
        Log.i("onMeasure","執行了onDraw");
        super.onDraw(canvas);

        mSin_1 = (float) Math.sin(Math.toRadians(1));
        // 大圓半徑
        float outerRadius = (getWidth() < getHeight() ? getWidth() : getHeight()) / 2f;
        //小圓點半徑
        float dotRadius = mSin_1 * outerRadius / (1 + mSin_1);
        float centerX = getWidth() / 2f;
        float centerY = getHeight() / 2f;

        mPaint.setStyle(Paint.Style.FILL);
        int count = 0;
        while (count++ < 50) {
            canvas.drawCircle(centerX, centerY - outerRadius + dotRadius, dotRadius, mPaint);
            canvas.rotate(7.2f, centerX, centerY);
        }
    }
複製程式碼

第二部分

  1. 繪製矩形形成的圓,原理也是一樣的,只不過把繪製小藍圓變成繪製矩形。繪製矩形需要確定左上角和右下角這兩個點的位置就可以了。然後將畫布旋轉某一角度值繼續繪製即可。這裡繪製了50次,每次旋轉10度,總共是500度>360度,保證大於360度即可,多餘的會重複覆蓋,但如果小於360度,就會導致繪製殘缺。

android 自定義酷炫進度條動畫

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

        mPaint.setShader(null);
        mPaint.setAntiAlias(true); // 抗鋸齒
        mPaint.setDither(true); // 防抖動

        // 半徑,這裡減去40是將半徑縮小40
        float outerRadius = (getWidth() < getHeight() ? getWidth() : getHeight()) / 2f-40;
        float centerX = getWidth() / 2f;
        float centerY = getHeight() / 2f;

        mPaint.setStyle(Paint.Style.FILL);
        int count = 0;
        int des = (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 6, getResources().getDisplayMetrics());
        while (count++ < 50) {
            canvas.drawRect(centerX-3,centerY-outerRadius,centerX+3,centerY-outerRadius+des,
                    mPaint);
            canvas.rotate(10.0f, centerX, centerY);
        }
    }
複製程式碼

第三部分

  1. 繪製圓環和圓,繪製文字這幾個是放到一個View處理的,因為不用涉及到旋轉,所以可以放到一起繪製。這部分是參考了另外一篇部落格,有興趣的讀者也可以享用 自定義進度條
  2. 畫圓這個比較簡單不說,圓環的話,其實就是畫圓弧,畫圓弧的時候會先確定一個外接矩形,設定畫筆型別為描邊circlePaint.setStyle(Paint.Style.STROKE);同時不包含圓心,就可以了。具體的可以看下這篇部落格 drawArc畫圓弧介紹 ,這裡不便展開。
private void drawCircle(Canvas canvas, int center, int radius)
    {
        //畫一個簡單的圓
        firstPaint.setShader(null); // 清除上一次的shader
        firstPaint.setColor(firstColor); // 設定底部圓環的顏色,這裡使用第一種顏色
        firstPaint.setStyle(Paint.Style.STROKE); // 設定繪製的圓為空心
        canvas.drawCircle(center, center, radius+40, firstPaint); 
        
        //畫一個圓環
        RectF oval = new RectF(center - radius, center - radius, center + radius, center + radius);
        circlePaint.setShader(null);
        // 繪製顏色漸變圓環
        // shader類是Android在圖形變換中非常重要的一個類。Shader在三維軟體中我們稱之為著色器,其作用是來給影象著色。
        LinearGradient linearGradient = new LinearGradient(circleWidth, circleWidth, getMeasuredWidth()
                - circleWidth, getMeasuredHeight() - circleWidth, colorArray, null, Shader.TileMode.MIRROR);
        circlePaint.setShader(linearGradient);
        //這裡注意設定為描邊型別
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setShadowLayer(10, 10, 10, Color.RED);
        circlePaint.setColor(secondColor); // 設定圓弧的顏色
        circlePaint.setStrokeCap(Paint.Cap.ROUND); // 把每段圓弧改成圓角的
        // 計算每次畫圓弧時掃過的角度,這裡計算要注意分母要轉為float型別,否則alphaAngle永遠為0
        alphaAngle = currentValue * 360.0f / maxValue * 1.0f;
        canvas.drawArc(oval, -90, alphaAngle, false, circlePaint);
    }
複製程式碼
  1. 可以看到繪製圓環的時候,用到alphaAngle,這個是掃過的角度,根據currentValue當前的進度值,得出當前掃過的角度。maxValue是100最大進度值。更新進度的時候會呼叫以下這個方法進行更新,在invalidate()被呼叫後,View會進行重繪,回撥onDraw()方法,得出新的alphaAngle角度值,繪製出對應進度的圓環。
public void setProgress(int progress)
    {
        int percent = progress * maxValue / 100;
        if (percent < 0)
        {
            percent = 0;
        }
        if (percent > 100)
        {
            percent = 100;
        }
        this.currentValue = percent;
        //更新View
        invalidate();
    }
複製程式碼
  1. 繪製文字也是同樣的道理,通過setProgress()方法來更新currentValue值。這裡繪製文字有個注意的地方:文字的繪製位置居中。drawText()方法的第二三個引數分別是Text文字x,y座標,這裡為什麼設定為center和baseline這兩個值是有原因的,牆裂推薦這篇部落格瞭解原因:drawText介紹 這部分的主要程式碼如下:
private void drawText(Canvas canvas, int center, int radius)
    {
        float result = (currentValue * 100.0f / maxValue * 1.0f); // 計算進度
        String percent = String.format("%.1f", result) + "%";

        textPaint.setTextAlign(Paint.Align.CENTER); // 設定文字居中,文字的x座標要注意
        textPaint.setColor(textColor); // 設定文字顏色
        textPaint.setTextSize(40); // 設定要繪製的文字大小
        textPaint.setStrokeWidth(0); // 注意此處一定要重新設定寬度為0,否則繪製的文字會重疊
        Rect bounds = new Rect(); // 文字邊框
        textPaint.getTextBounds(percent, 0, percent.length(), bounds); // 獲得繪製文字的邊界矩形
        Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt(); // 獲取繪製Text時的四條線
        int baseline = center + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom; 
        canvas.drawText(percent, center, baseline, textPaint); // 繪製表示進度的文字
    }
複製程式碼

總結

  1. 寫到這其實還沒完,只是對裡面的重點剖析了下,剩下的細節讀者可以看下原始碼,我也封裝成了一個依賴庫,有需要可以使用。對應的demo使用放到github上,喜歡的可以點個star。
  • 在專案的build.gradle中新增:
allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}
複製程式碼
  • 在module的build.gradle中新增:
implementation 'com.github.guoxiaozhou:AnimationDemo:0.5'
複製程式碼
  • 具體使用demo地址:github.com/guoxiaozhou… 專案中包含依賴庫原始碼,有興趣的可以看看細節,有問題或者錯誤直接聯絡我

相關文章