Android自定義View實(shí)現(xiàn)打鉤動畫功能
先上效果圖
動圖

靜態(tài)圖

1. 回顧
【Android自定義View:一個(gè)精致的打鉤小動畫】上一篇文章,我們已經(jīng)實(shí)現(xiàn)了基本上實(shí)現(xiàn)了控件的效果了,但是...但是...過了三四天后,仔細(xì)看回自己寫的代碼,雖然思路還在,但是部分代碼還是不能一下子的看得明白...
我的天,這得立馬重構(gòu)啊~ 恰好,有個(gè)簡友 ChangQin 模仿寫了一下這個(gè)控件,我看了后覺得我也可以這樣實(shí)現(xiàn)一下。
2. 深思
關(guān)于控件繪制的思路,可以去看看 上一篇文章,這里就不再分析了。這里先來分析一下上一篇文章里面,控件里面的一些頑處,哪些地方需要改進(jìn)。
就拿 繪制圓環(huán)進(jìn)度 這一步來看
//計(jì)數(shù)器
private int ringCounter = 0;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isChecked) {
...
return;
}
//畫圓弧進(jìn)度,每次繪制都自加12個(gè)單位,也就是圓弧又掃過了12度
//這里的12個(gè)單位先寫死,后面我們可以做一個(gè)配置來實(shí)現(xiàn)自定義
ringCounter += 12;
if (ringCounter >= 360) {
ringCounter = 360;
}
canvas.drawArc(mRectF, 90, ringCounter, false, mPaintRing);
...
//強(qiáng)制重繪
postInvalidate();
}
這里,我們定義了一個(gè)計(jì)數(shù)器ringCounter, 當(dāng)繪制的時(shí)候,是根據(jù)12個(gè)單位進(jìn)行自增到達(dá)360,從而模擬進(jìn)度的變化。
仔細(xì)想想
通過改變自增的單位來控制動畫速度的變化,這很難調(diào)整得使自己滿意,此時(shí)我們可以想到,使動畫速度執(zhí)行快慢的根本就是控制時(shí)間啊,如果可以用時(shí)間來控制動畫速度那得方便多了動畫分為4步執(zhí)行,如果每一步動畫都用手寫計(jì)數(shù)器來實(shí)現(xiàn),那得定義4個(gè)成員變量或者更多,太多成員變量只會讓代碼更加混亂如果動畫要加上插值器,那手寫的計(jì)數(shù)器根本無法滿足看到上面的分析,我無法接受了
3. 改改改
那么怎么去改善上面所說的問題呢,答案就是用自定義的屬性動畫來解決了,所以這篇文章主要的講的地方就是用屬性動畫來替換手寫的計(jì)數(shù)器,盡可能的保證代碼邏輯的清晰,特別是onDraw()方法中的代碼。
使用屬性動畫的一個(gè)好處就是,給定數(shù)值的范圍,它會幫你生成一堆你想要的數(shù)值,配合插值器還要意想不到的效果呢,下一面就一步一步針對動畫執(zhí)行的部分進(jìn)行重構(gòu)
3.1 繪制圓環(huán)進(jìn)度條
首先,使用自定義的ObjectAnimator來模擬進(jìn)度
//ringProgress是自定義的屬性名稱,生成數(shù)值的范圍是0 - 360,就是一個(gè)圓的角度 ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360); //定義動畫執(zhí)行的時(shí)間,很好的替代之前使用自增的單位來控制動畫執(zhí)行的速度 mRingAnimator.setDuration(mRingAnimatorDuration); //暫時(shí)不需要插值器 mRingAnimator.setInterpolator(null);
自定義屬性動畫,還需要配置相應(yīng)的setter和getter,因?yàn)樵趧赢媹?zhí)行的時(shí)候,會找相應(yīng)的setter去改變相應(yīng)的值。
private int getRingProgress() {
return ringProgress;
}
private void setRingProgress(int ringProgress) {
//動畫執(zhí)行的時(shí)候,會調(diào)用setter
//這里我們可以將動畫生成的數(shù)值記錄下來,用變量存起來,在ondraw的時(shí)候用
this.ringProgress = ringProgress;
//記得重繪
postInvalidate();
}
最后,在onDraw()中畫圖
//畫圓弧進(jìn)度canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);
3.2 繪制向圓心收縮的動畫
同理,也是造一個(gè)屬性動畫
//這里自定義的屬性是圓收縮的半徑 ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0); //加一個(gè)減速的插值器 mCircleAnimator.setInterpolator(new DecelerateInterpolator()); mCircleAnimator.setDuration(mCircleAnimatorDuration);
setter/getter也是類似就不說了
最后onDraw()中繪制
//畫背景
mPaintCircle.setColor(checkBaseColor);
canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle);
//當(dāng)進(jìn)度圓環(huán)繪制好了,就畫收縮的圓
if (ringProgress == 360) {
mPaintCircle.setColor(checkTickColor);
canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
}
3.3 繪制鉤和放大再回彈的效果
這是兩個(gè)獨(dú)立的效果,這里同時(shí)執(zhí)行,我就合在一起說了
首先也是定義屬性動畫
//勾出來的透明漸變 ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255); mAlphaAnimator.setDuration(200); //最后的放大再回彈的動畫,改變畫筆的寬度來實(shí)現(xiàn) //而畫筆的寬度,則是的變化范圍是 //首先從初始化寬度開始,再到初始化寬度的n倍,最后又回到初始化的寬度 ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES); mScaleAnimator.setInterpolator(null); mScaleAnimator.setDuration(mScaleAnimatorDuration); //打鉤和放大回彈的動畫一起執(zhí)行 AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet(); mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);
getter/setter
private int getTickAlpha() {
return 0;
}
private void setTickAlpha(int tickAlpha) {
//設(shè)置透明度,可以不用變量來保存了
//直接將透明度的值設(shè)置到畫筆里面即可
mPaintTick.setAlpha(tickAlpha);
postInvalidate();
}
private float getRingStrokeWidth() {
return mPaintRing.getStrokeWidth();
}
private void setRingStrokeWidth(float strokeWidth) {
//設(shè)置畫筆寬度,可以不用變量來保存了
//直接將畫筆寬度設(shè)置到畫筆里面即可
mPaintRing.setStrokeWidth(strokeWidth);
postInvalidate();
}
最后,同理在onDraw()中繪制即可
if (circleRadius == 0) {
canvas.drawLines(mPoints, mPaintTick);
canvas.drawArc(mRectF, 0, 360, false, mPaintRing);
}
3.4 依次執(zhí)行動畫
執(zhí)行多個(gè)動畫,可以用到AnimatorSet,其中playTogether()是一起執(zhí)行,playSequentially()是一個(gè)挨著一個(gè),step by step執(zhí)行。
mFinalAnimatorSet = new AnimatorSet(); mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);
最后在onDraw()中執(zhí)行動畫
//這里定義了一個(gè)標(biāo)識符,用于告訴程序,動畫每次只能執(zhí)行一次
if (!isAnimationRunning) {
isAnimationRunning = true;
//執(zhí)行動畫
mFinalAnimatorSet.start();
}
3.5 每個(gè)方法最好能有單一的職責(zé)
如果將定義屬性動畫的方法放在onDraw()中,我個(gè)人感覺很亂,并且再仔細(xì)看看,這幾個(gè)屬性動畫是不需要動態(tài)變化的,為什么不抽出來在一開始的時(shí)候就初始化呢?
so,我們將定義屬性動畫的代碼抽出來,并且放到構(gòu)造函數(shù)中初始化
public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
...
initAnimatorCounter();
}
/**
* 用ObjectAnimator初始化一些計(jì)數(shù)器
*/
private void initAnimatorCounter() {
//圓環(huán)進(jìn)度
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);
...
//最后的放大再回彈的動畫,改變畫筆的寬度來實(shí)現(xiàn)
ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES);
...
//打鉤和放大回彈的動畫一起執(zhí)行
AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);
mFinalAnimatorSet = new AnimatorSet();
mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);
}
最后,onDraw()方法中,只負(fù)責(zé)簡單的繪制,什么都不管
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isChecked) {
canvas.drawArc(mRectF, 90, 360, false, mPaintRing);
canvas.drawLines(mPoints, mPaintTick);
return;
}
//畫圓弧進(jìn)度
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動畫替換計(jì)數(shù)器
if (!isAnimationRunning) {
isAnimationRunning = true;
mFinalAnimatorSet.start();
}
}
最終效果是一樣的,代碼邏輯一目了然

所以,個(gè)人覺得,在開發(fā)中,定時(shí)review一下自己的代碼,無論對自己,還是對以后維護(hù),是很有幫助的。
That ' s all~感謝大家閱讀,最后再放一下項(xiàng)目的github地址
> Github地址:TickView,一個(gè)精致的打鉤小動畫https://github.com/ChengangFeng/TickView
以上就是小編給大家整理的關(guān)于自定義View實(shí)現(xiàn)打鉤動畫功能的全部內(nèi)容,大家可以測試下,如果還有任何問題可以在下方的留言區(qū)討論,感謝對腳本之家的支持。
相關(guān)文章
Android無需讀寫權(quán)限通過臨時(shí)授權(quán)讀寫用戶文件詳解
這篇文章主要為大家介紹了Android無需讀寫權(quán)限通過臨時(shí)授權(quán)讀寫用戶文件詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
Android Studio配置(Android Studio4.1為例)
這篇文章主要介紹了Android Studio配置(Android Studio4.1為例),文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
Android仿今日頭條頂部導(dǎo)航欄效果的實(shí)例代碼
這篇文章主要介紹了Android之仿今日頭條頂部導(dǎo)航欄效果的實(shí)例代碼,具有很好的參考價(jià)值,希望對大家有所幫助,一起跟隨小編過來看看吧2018-05-05
android studio 3.4配置Android -jni 開發(fā)基礎(chǔ)的教程詳解
這篇文章主要介紹了android studio 3.4配置Android -jni 開發(fā)基礎(chǔ),本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09
Android藍(lán)牙通信聊天實(shí)現(xiàn)發(fā)送和接受功能
這篇文章主要為大家詳細(xì)介紹了Android藍(lán)牙通信聊天實(shí)現(xiàn)發(fā)送和接受功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07
Android編程實(shí)現(xiàn)調(diào)用系統(tǒng)分享功能示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)調(diào)用系統(tǒng)分享功能,結(jié)合實(shí)例形式分析了Android實(shí)現(xiàn)針對文字、圖片等元素分享功能的相關(guān)操作技巧,需要的朋友可以參考下2017-01-01
A07_TimePicker & DatePicker & AnalogClock & Digi
本文將帶領(lǐng)大家一起學(xué)習(xí)時(shí)間日期和時(shí)鐘的設(shè)置。A07_TimePicker & DatePicker & AnalogClock & DigitalClock 的設(shè)置,感興趣的朋友可以參考下哈2013-06-06
Android開發(fā)之高德地圖實(shí)現(xiàn)定位
本篇文章主要介紹了Android中高德地圖實(shí)現(xiàn)定位的相關(guān)知識。具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-04-04
Android無需權(quán)限調(diào)起系統(tǒng)相機(jī)
在進(jìn)行一些小型APP的開發(fā),或者是對拍照界面沒有自定義要求時(shí),我們可以用調(diào)起系統(tǒng)相機(jī)的方式快速完成拍照需求2023-03-03

