1. 程式人生 > >Android動畫篇(一):圓形進度條CircleProgressBar

Android動畫篇(一):圓形進度條CircleProgressBar

前言

最近看框架和原始碼比較多,很久沒有寫動畫了,相信很多的朋友都對動畫感興趣,我也不例外,畢竟做前端還是要靠動畫特效吃飯的,並且比寫功能模組更有成就感。

今天我們就來個稍微簡單一點的CircleProgressBar熱個身。

首先需要對ValueAnimator動畫,還有Canvas,Paint畫圖的相關的類和API都有一定的瞭解,所以這部分還比較薄弱的朋友可以先去學習一下基礎知識,否則可能會有些吃力。

正文

先看一下效果圖,我不會錄屏,就百度了一張圖片:

這裡寫圖片描述

大概是這樣的效果,首先我們不考慮效果,先畫出這個圓形的進度條,新建檔案CircleProgressBar:

/**
     * 進度
     */
private float mProgress = 50; /** * 最大進度 */ private int mMaxProgress = 100; /** * 繪製進度條 */ private void drawProgress(Canvas canvas) { // 開始畫進度條 // 首先畫出背景圓 mPaint.setColor(mBackgroundColor); mPaint.setStyle(Paint.Style.FILL); // 這裡減去了邊框的寬度
canvas.drawCircle(mWidth / 2, mHeight / 2, mRadius - mBorderWidth, mPaint); // 畫出進度條 mPaint.setColor(mProgressBorderColor); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mBorderWidth); // 計算圓弧劃過的角度 float angle = CIRCULAR / mMaxProgress * mProgress; // 這裡要畫圓弧
canvas.drawArc(mContentRectF, -90, angle, false, mPaint); // 畫出補全部分的進度條 mPaint.setColor(mBorderColor); mPaint.setStrokeWidth(mBorderWidth); // 這裡要畫圓弧 canvas.drawArc(mContentRectF, -90 + angle, CIRCULAR - angle, false, mPaint); }

先話出背景色的圓,然後畫出進度條的顏色的邊框,再畫出進度條以外的部分,為了顯示的明顯,我分別用了三個顏色,最終的效果:

這裡寫圖片描述

最初的樣子已經出來了,但是有一個小細節要注意:

這裡貼出mMaxProgress = 100,為什麼不是1000,10000呢?當然也可以,但是我不推薦這個數字過大,大家可以去看看系統自帶的ProgressBar,他的註釋有提醒開發者,不要使用過大的max,最好是100,感興趣的可以去看一看。

現在就差動畫了,接下來我們來分析一下動畫:

1、首先進度會飛快的上漲,以順時針為方向,伸長的部分是頭部。
2、然後進度會飛速的下降,以順時針為方向,縮短的部分是尾部。

首先我們來完成第一部分:

/**
     * 開始過度動畫
     */
    private void startIntermediateAnim() {
        if (valueAnimator == null) {
            valueAnimator = new ValueAnimator().ofFloat(0, mMaxProgress);
            valueAnimator.setDuration(DURATION);
            valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                // 設定進度
                    float value = (float) valueAnimator.getAnimatedValue();
                    setProgress(value);
                }

            });
        valueAnimator.setRepeatCount(-1);
        valueAnimator.start();
    }

也是沒什麼太多的技術含量,但是有幾點需要說明一下:

我們使用ValueAnimator().ofFloat,是為了動畫的流暢性,如果你使用了int,你會發現動畫會有一些細微的卡頓,因為int型捨棄了小數部分,這樣就會出現誤差,視覺上就會出現卡頓。

伸長的動畫已經成型了,那縮短的動畫不就簡單了,直接動畫reverse不就好了?我激動得設定了:

valueAnimator = new ValueAnimator().ofFloat(0, mMaxProgress,0);

或者是

valueAnimator.setRepeatMode(ValueAnimator.REVERSE);

我迫不及待的運行了程式碼,臥草草,竟然不行?仔細觀察效果,我們發現了問題:

如果是縮短,還是從順時針的頭部縮短,而不是從尾部,這是為什麼呢?其實從api也可以理解,因為我們是從起始位置開始畫弧,只要起始位置不變,尾部肯定不會發生變化。

雖然明白了這個道理,但是心情非常的壓抑,難道就要放棄了?突然靈光一閃,我發現了一個神奇的辦法,不知道機智的小夥伴是不是也想到了:

還記得我之前的繪圖步驟嗎?
先繪製進度部分,然後剩餘部分不全。

既然進度部分只能頭部伸長,尾部也是一樣,那我讓補全部分伸長,那進度部分的尾部不就是縮短了嗎?

那如何讓補全部分伸長呢?

1、把progress在伸長結束時,開始讓補全部分使用progress,從而讓他伸長,但是這樣會改變原有的功能邏輯,非常危險,工作量也大。

2、最簡單的辦法,把進度的顏色不補全部分的顏色交換,然後把位置也互換,不就OK了?

經過簡單的修改之後:

if (valueAnimator == null) {
            valueAnimator = new ValueAnimator().ofFloat(0, mMaxProgress);
            valueAnimator.setDuration(DURATION);
            valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    float value = (float) valueAnimator.getAnimatedValue();
                    setProgress(value);
                }

            });
            valueAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animator) {

                }

                @Override
                public void onAnimationEnd(Animator animator) {
            // 因為是迴圈動畫,所以這裡不會回撥
                }

                @Override
                public void onAnimationCancel(Animator animator) {

                }

                @Override
                public void onAnimationRepeat(Animator animator) {
                    // 互換兩者的顏色
                    int color = getProgressBackgroundColor();
                    setProgressBackgroundColor(getProgressColor());
                    setProgressColor(color);
                }
            });
        }
        valueAnimator.setRepeatCount(-1);
        valueAnimator.start();

ok,還有誰?最關鍵的部分已經全部完成了,還差最後一點點,仔細的觀察效果圖,發現進度條是有最小進度的,沒有完全消失,所以我們再設定一個最小進度,並且每次設定進度的時候,我們都稍微旋轉一下角度,這樣就會一邊伸長縮短一邊旋轉了:

/**
     * 開始過度動畫
     */
    private void startIntermediateAnim() {
        if (valueAnimator == null) {
            valueAnimator = new ValueAnimator().ofFloat(mMinProgress, mMaxProgress - mMinProgress);
            valueAnimator.setDuration(DURATION);
            valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    float value = (float) valueAnimator.getAnimatedValue();
                    setProgress(value);
                    // 每次旋轉2度
                    mStartAngle += 2;
                }

            });
            valueAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animator) {

                }

                @Override
                public void onAnimationEnd(Animator animator) {

                }

                @Override
                public void onAnimationCancel(Animator animator) {

                }

                @Override
                public void onAnimationRepeat(Animator animator) {
                // 因為有了最小進度,所以每次都要位置設定到補全部分的位置
                    mStartAngle = mStartAngle - CIRCULAR / mMaxProgress * mMinProgress;
                    // 互換兩者的顏色
                    int color = getProgressBackgroundColor();
                    setProgressBackgroundColor(getProgressColor());
                    setProgressColor(color);
                }
            });
        }
        valueAnimator.setRepeatCount(-1);
        valueAnimator.start();
    }

最終的效果,就想一開始的效果圖一樣,這裡不貼了。

總結

看上去稍微有點複雜的動畫,經過我們的分析拆解,就變得很簡單了。如果是利用Translation, Rotate 這樣的動畫,那實現起來真是太難了,ValueAnimator就是從他們中分離出來的專門用來計算差值的強大武器,有了它我們開發一些高階的效果,就簡單多了。

我對demo進行了一些修改,即可以是普通的圓形進度條,也可以是loading的動畫,就想progressBar一樣,大家可以下載下來,參考一下。

ok,今天就到這裡了,明天就是週末了,祝大家浪起來~