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,今天就到這裡了,明天就是週末了,祝大家浪起來~