Github地址:TickView,一個精緻的打鉤小動畫
github.com/ChengangFen…java
先上效果圖,否則讀不下去了,right?git
動圖github
靜態圖
canvas
【Android自定義View:一個精緻的打鉤小動畫】
上一篇文章,咱們已經實現了基本上實現了控件的效果了,可是...可是...過了三四天後,仔細看回本身寫的代碼,雖然思路還在,可是部分代碼仍是不能一會兒的看得明白...ide
個人天,這得立馬重構啊~ 剛好,有個網友 ChangQin 模仿寫了一下這個控件,我看了後以爲我也能夠這樣實現一下。函數
關於控件繪製的思路,能夠去看看 上一篇文章,這裏就再也不分析了。
這裏先來分析一下上一篇文章裏面,控件裏面的一些頑處,哪些地方須要改進。post
就拿 繪製圓環進度 這一步來看動畫
//計數器
private int ringCounter = 0;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isChecked) {
...
return;
}
//畫圓弧進度,每次繪製都自加12個單位,也就是圓弧又掃過了12度
//這裏的12個單位先寫死,後面咱們能夠作一個配置來實現自定義
ringCounter += 12;
if (ringCounter >= 360) {
ringCounter = 360;
}
canvas.drawArc(mRectF, 90, ringCounter, false, mPaintRing);
...
//強制重繪
postInvalidate();
}複製代碼
這裏,咱們定義了一個計數器ringCounter
, 當繪製的時候,是根據12個單位進行自增到達360,從而模擬進度的變化。this
仔細想一想spa
那麼怎麼去改善上面所說的問題呢,答案就是用自定義的屬性動畫來解決了,因此這篇文章主要的講的地方就是用屬性動畫來替換手寫的計數器,儘量的保證代碼邏輯的清晰,特別是onDraw()
方法中的代碼。
使用屬性動畫的一個好處就是,給定數值的範圍,它會幫你生成一堆你想要的數值,配合插值器還要意想不到的效果呢,下一面就一步一步針對動畫執行的部分進行重構
首先,使用自定義的ObjectAnimator
來模擬進度
//ringProgress是自定義的屬性名稱,生成數值的範圍是0 - 360,就是一個圓的角度
ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360);
//定義動畫執行的時間,很好的替代以前使用自增的單位來控制動畫執行的速度
mRingAnimator.setDuration(mRingAnimatorDuration);
//暫時不須要插值器
mRingAnimator.setInterpolator(null);複製代碼
自定義屬性動畫,還須要配置相應的setter
和getter
,由於在動畫執行的時候,會找相應的setter
去改變相應的值。
private int getRingProgress() {
return ringProgress;
}
private void setRingProgress(int ringProgress) {
//動畫執行的時候,會調用setter
//這裏咱們能夠將動畫生成的數值記錄下來,用變量存起來,在ondraw的時候用
this.ringProgress = ringProgress;
//記得重繪
postInvalidate();
}複製代碼
最後,在onDraw()
中畫圖
//畫圓弧進度
canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);複製代碼
同理,也是造一個屬性動畫
//這裏自定義的屬性是圓收縮的半徑
ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0);
//加一個減速的插值器
mCircleAnimator.setInterpolator(new DecelerateInterpolator());
mCircleAnimator.setDuration(mCircleAnimatorDuration);複製代碼
setter/getter也是相似就不說了
最後onDraw()
中繪製
//畫背景
mPaintCircle.setColor(checkBaseColor);
canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle);
//當進度圓環繪製好了,就畫收縮的圓
if (ringProgress == 360) {
mPaintCircle.setColor(checkTickColor);
canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
}複製代碼
這是兩個獨立的效果,這裏同時執行,我就合在一塊兒說了
首先也是定義屬性動畫
//勾出來的透明漸變
ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255);
mAlphaAnimator.setDuration(200);
//最後的放大再回彈的動畫,改變畫筆的寬度來實現
//而畫筆的寬度,則是的變化範圍是
//首先從初始化寬度開始,再到初始化寬度的n倍,最後又回到初始化的寬度
ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES);
mScaleAnimator.setInterpolator(null);
mScaleAnimator.setDuration(mScaleAnimatorDuration);
//打鉤和放大回彈的動畫一塊兒執行
AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);複製代碼
getter/setter
private int getTickAlpha() {
return 0;
}
private void setTickAlpha(int tickAlpha) {
//設置透明度,能夠不用變量來保存了
//直接將透明度的值設置到畫筆裏面便可
mPaintTick.setAlpha(tickAlpha);
postInvalidate();
}
private float getRingStrokeWidth() {
return mPaintRing.getStrokeWidth();
}
private void setRingStrokeWidth(float strokeWidth) {
//設置畫筆寬度,能夠不用變量來保存了
//直接將畫筆寬度設置到畫筆裏面便可
mPaintRing.setStrokeWidth(strokeWidth);
postInvalidate();
}複製代碼
最後,同理在onDraw()
中繪製便可
if (circleRadius == 0) {
canvas.drawLines(mPoints, mPaintTick);
canvas.drawArc(mRectF, 0, 360, false, mPaintRing);
}複製代碼
執行多個動畫,能夠用到AnimatorSet
,其中playTogether()
是一塊兒執行,playSequentially()
是一個挨着一個,step by step執行。
mFinalAnimatorSet = new AnimatorSet();
mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);複製代碼
最後在onDraw()
中執行動畫
//這裏定義了一個標識符,用於告訴程序,動畫每次只能執行一次
if (!isAnimationRunning) {
isAnimationRunning = true;
//執行動畫
mFinalAnimatorSet.start();
}複製代碼
若是將定義屬性動畫的方法放在onDraw()
中,我我的感受很亂,而且再仔細看看,這幾個屬性動畫是不須要動態變化的,爲何不抽出來在一開始的時候就初始化呢?
so,咱們將定義屬性動畫的代碼抽出來,而且放到構造函數中初始化
public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
...
initAnimatorCounter();
}複製代碼
/** * 用ObjectAnimator初始化一些計數器 */
private void initAnimatorCounter() {
//圓環進度
ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360);
...
//收縮動畫
ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0);
...
//勾出來的透明漸變
ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255);
...
//最後的放大再回彈的動畫,改變畫筆的寬度來實現
ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES);
...
//打鉤和放大回彈的動畫一塊兒執行
AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);
mFinalAnimatorSet = new AnimatorSet();
mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);
}複製代碼
最後,onDraw()
方法中,只負責簡單的繪製,什麼都無論
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isChecked) {
canvas.drawArc(mRectF, 90, 360, false, mPaintRing);
canvas.drawLines(mPoints, mPaintTick);
return;
}
//畫圓弧進度
canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);
//畫黃色的背景
mPaintCircle.setColor(checkBaseColor);
canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle);
//畫收縮的白色圓
if (ringProgress == 360) {
mPaintCircle.setColor(checkTickColor);
canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
}
//畫勾,以及放大收縮的動畫
if (circleRadius == 0) {
canvas.drawLines(mPoints, mPaintTick);
canvas.drawArc(mRectF, 0, 360, false, mPaintRing);
}
//ObjectAnimator動畫替換計數器
if (!isAnimationRunning) {
isAnimationRunning = true;
mFinalAnimatorSet.start();
}
}複製代碼
最終效果是同樣的,代碼邏輯一目瞭然
因此,我的以爲,在開發中,定時review一下本身的代碼,不管對本身,仍是對之後維護,是頗有幫助的。
That ' s all~
感謝你們閱讀,最後再放一下項目的github地址
Github地址:TickView,一個精緻的打鉤小動畫
github.com/ChengangFen…