Android自定義View實(shí)現(xiàn)打鉤動(dòng)畫功能
先上效果圖
動(dòng)圖
靜態(tài)圖
1. 回顧
【Android自定義View:一個(gè)精致的打鉤小動(dòng)畫】上一篇文章,我們已經(jīng)實(shí)現(xiàn)了基本上實(shí)現(xiàn)了控件的效果了,但是...但是...過(guò)了三四天后,仔細(xì)看回自己寫的代碼,雖然思路還在,但是部分代碼還是不能一下子的看得明白...
我的天,這得立馬重構(gòu)啊~ 恰好,有個(gè)簡(jiǎn)友 ChangQin 模仿寫了一下這個(gè)控件,我看了后覺(jué)得我也可以這樣實(shí)現(xiàn)一下。
2. 深思
關(guān)于控件繪制的思路,可以去看看 上一篇文章,這里就不再分析了。這里先來(lái)分析一下上一篇文章里面,控件里面的一些頑處,哪些地方需要改進(jìn)。
就拿 繪制圓環(huán)進(jìn)度 這一步來(lái)看
//計(jì)數(shù)器 private int ringCounter = 0; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (!isChecked) { ... return; } //畫圓弧進(jìn)度,每次繪制都自加12個(gè)單位,也就是圓弧又掃過(guò)了12度 //這里的12個(gè)單位先寫死,后面我們可以做一個(gè)配置來(lái)實(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ì)想想
通過(guò)改變自增的單位來(lái)控制動(dòng)畫速度的變化,這很難調(diào)整得使自己滿意,此時(shí)我們可以想到,使動(dòng)畫速度執(zhí)行快慢的根本就是控制時(shí)間啊,如果可以用時(shí)間來(lái)控制動(dòng)畫速度那得方便多了動(dòng)畫分為4步執(zhí)行,如果每一步動(dòng)畫都用手寫計(jì)數(shù)器來(lái)實(shí)現(xiàn),那得定義4個(gè)成員變量或者更多,太多成員變量只會(huì)讓代碼更加混亂如果動(dòng)畫要加上插值器,那手寫的計(jì)數(shù)器根本無(wú)法滿足看到上面的分析,我無(wú)法接受了
3. 改改改
那么怎么去改善上面所說(shuō)的問(wèn)題呢,答案就是用自定義的屬性動(dòng)畫來(lái)解決了,所以這篇文章主要的講的地方就是用屬性動(dòng)畫來(lái)替換手寫的計(jì)數(shù)器,盡可能的保證代碼邏輯的清晰,特別是onDraw()
方法中的代碼。
使用屬性動(dòng)畫的一個(gè)好處就是,給定數(shù)值的范圍,它會(huì)幫你生成一堆你想要的數(shù)值,配合插值器還要意想不到的效果呢,下一面就一步一步針對(duì)動(dòng)畫執(zhí)行的部分進(jìn)行重構(gòu)
3.1 繪制圓環(huán)進(jìn)度條
首先,使用自定義的ObjectAnimator
來(lái)模擬進(jìn)度
//ringProgress是自定義的屬性名稱,生成數(shù)值的范圍是0 - 360,就是一個(gè)圓的角度 ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360); //定義動(dòng)畫執(zhí)行的時(shí)間,很好的替代之前使用自增的單位來(lái)控制動(dòng)畫執(zhí)行的速度 mRingAnimator.setDuration(mRingAnimatorDuration); //暫時(shí)不需要插值器 mRingAnimator.setInterpolator(null);
自定義屬性動(dòng)畫,還需要配置相應(yīng)的setter
和getter
,因?yàn)樵趧?dòng)畫執(zhí)行的時(shí)候,會(huì)找相應(yīng)的setter
去改變相應(yīng)的值。
private int getRingProgress() { return ringProgress; } private void setRingProgress(int ringProgress) { //動(dòng)畫執(zhí)行的時(shí)候,會(huì)調(diào)用setter //這里我們可以將動(dòng)畫生成的數(shù)值記錄下來(lái),用變量存起來(lái),在ondraw的時(shí)候用 this.ringProgress = ringProgress; //記得重繪 postInvalidate(); }
最后,在onDraw()
中畫圖
//畫圓弧進(jìn)度canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);
3.2 繪制向圓心收縮的動(dòng)畫
同理,也是造一個(gè)屬性動(dòng)畫
//這里自定義的屬性是圓收縮的半徑 ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0); //加一個(gè)減速的插值器 mCircleAnimator.setInterpolator(new DecelerateInterpolator()); mCircleAnimator.setDuration(mCircleAnimatorDuration);
setter/getter也是類似就不說(shuō)了
最后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í)行,我就合在一起說(shuō)了
首先也是定義屬性動(dòng)畫
//勾出來(lái)的透明漸變 ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255); mAlphaAnimator.setDuration(200); //最后的放大再回彈的動(dòng)畫,改變畫筆的寬度來(lái)實(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); //打鉤和放大回彈的動(dòng)畫一起執(zhí)行 AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet(); mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);
getter/setter
private int getTickAlpha() { return 0; } private void setTickAlpha(int tickAlpha) { //設(shè)置透明度,可以不用變量來(lái)保存了 //直接將透明度的值設(shè)置到畫筆里面即可 mPaintTick.setAlpha(tickAlpha); postInvalidate(); } private float getRingStrokeWidth() { return mPaintRing.getStrokeWidth(); } private void setRingStrokeWidth(float strokeWidth) { //設(shè)置畫筆寬度,可以不用變量來(lái)保存了 //直接將畫筆寬度設(shè)置到畫筆里面即可 mPaintRing.setStrokeWidth(strokeWidth); postInvalidate(); }
最后,同理在onDraw()
中繪制即可
if (circleRadius == 0) { canvas.drawLines(mPoints, mPaintTick); canvas.drawArc(mRectF, 0, 360, false, mPaintRing); }
3.4 依次執(zhí)行動(dòng)畫
執(zhí)行多個(gè)動(dòng)畫,可以用到AnimatorSet
,其中playTogether()
是一起執(zhí)行,playSequentially()
是一個(gè)挨著一個(gè),step by step執(zhí)行。
mFinalAnimatorSet = new AnimatorSet(); mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);
最后在onDraw()
中執(zhí)行動(dòng)畫
//這里定義了一個(gè)標(biāo)識(shí)符,用于告訴程序,動(dòng)畫每次只能執(zhí)行一次 if (!isAnimationRunning) { isAnimationRunning = true; //執(zhí)行動(dòng)畫 mFinalAnimatorSet.start(); }
3.5 每個(gè)方法最好能有單一的職責(zé)
如果將定義屬性動(dòng)畫的方法放在onDraw()
中,我個(gè)人感覺(jué)很亂,并且再仔細(xì)看看,這幾個(gè)屬性動(dòng)畫是不需要?jiǎng)討B(tài)變化的,為什么不抽出來(lái)在一開始的時(shí)候就初始化呢?
so,我們將定義屬性動(dòng)畫的代碼抽出來(lái),并且放到構(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); ... //收縮動(dòng)畫 ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0); ... //勾出來(lái)的透明漸變 ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255); ... //最后的放大再回彈的動(dòng)畫,改變畫筆的寬度來(lái)實(shí)現(xiàn) ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES); ... //打鉤和放大回彈的動(dòng)畫一起執(zhí)行 AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet(); mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator); mFinalAnimatorSet = new AnimatorSet(); mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet); }
最后,onDraw()
方法中,只負(fù)責(zé)簡(jiǎn)單的繪制,什么都不管
@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); } //畫勾,以及放大收縮的動(dòng)畫 if (circleRadius == 0) { canvas.drawLines(mPoints, mPaintTick); canvas.drawArc(mRectF, 0, 360, false, mPaintRing); } //ObjectAnimator動(dòng)畫替換計(jì)數(shù)器 if (!isAnimationRunning) { isAnimationRunning = true; mFinalAnimatorSet.start(); } }
最終效果是一樣的,代碼邏輯一目了然
所以,個(gè)人覺(jué)得,在開發(fā)中,定時(shí)review一下自己的代碼,無(wú)論對(duì)自己,還是對(duì)以后維護(hù),是很有幫助的。
That ' s all~感謝大家閱讀,最后再放一下項(xiàng)目的github地址
> Github地址:TickView,一個(gè)精致的打鉤小動(dòng)畫https://github.com/ChengangFeng/TickView
以上就是小編給大家整理的關(guān)于自定義View實(shí)現(xiàn)打鉤動(dòng)畫功能的全部?jī)?nèi)容,大家可以測(cè)試下,如果還有任何問(wèn)題可以在下方的留言區(qū)討論,感謝對(duì)腳本之家的支持。
相關(guān)文章
Android編程實(shí)現(xiàn)畫板功能的方法總結(jié)【附源碼下載】
這篇文章主要介紹了Android編程實(shí)現(xiàn)畫板功能的方法,結(jié)合實(shí)例形式總結(jié)分析了Android基于自定義View與Canvas類實(shí)現(xiàn)畫板功能的具體操作步驟與相關(guān)注意事項(xiàng),需要的朋友可以參考下2018-02-02TextView顯示系統(tǒng)時(shí)間(時(shí)鐘功能帶秒針變化
用System.currentTimeMillis()可以獲取系統(tǒng)當(dāng)前的時(shí)間,我們可以開啟一個(gè)線程,然后通過(guò)handler發(fā)消息,來(lái)實(shí)時(shí)的更新TextView上顯示的系統(tǒng)時(shí)間,可以做一個(gè)時(shí)鐘的功能2013-11-11詳解Android 7.0 Settings 加載選項(xiàng)
本篇文章主要介紹了Android 7.0 Settings 加載選項(xiàng),Android 7.0 Settings頂部多了一個(gè)建議選項(xiàng),多了個(gè)側(cè)邊欄,操作更加便捷了,有興趣的可以了解一下。2017-02-02Android中系統(tǒng)默認(rèn)輸入法設(shè)置的方法(輸入法的顯示和隱藏)
這篇文章主要介紹了Android中系統(tǒng)默認(rèn)輸入法設(shè)置的方法(輸入法的顯示和隱藏)的相關(guān)資料,需要的朋友可以參考下2016-01-01Android 定位系統(tǒng)(GPS)開發(fā)詳解
GPS定位是智能手機(jī)上一個(gè)比較有意思的功能,LBS等服務(wù)都有效的利用了GPS定位功能,本文就跟大家分享下Android開發(fā)中的GPS定位知識(shí)2016-07-07Android平臺(tái)下輕量級(jí)http網(wǎng)絡(luò)傳輸庫(kù)
這篇文章主要介紹了Android平臺(tái)下輕量級(jí)http網(wǎng)絡(luò)傳輸庫(kù)的相關(guān)資料,需要的朋友可以參考下2016-01-01怎么發(fā)布打包并發(fā)布自己的Android應(yīng)用(APP)
前面我為大家講的都是關(guān)于Android開發(fā)方面的知識(shí)點(diǎn)和技術(shù),不少朋友可能會(huì)感到疑惑--究竟我該怎么打包、發(fā)布自己開發(fā)的APP,怎樣將我的APP放到網(wǎng)上工別人下載,怎樣保證我的APP安全及版權(quán)問(wèn)題呢2013-11-11android studio更新gradle錯(cuò)誤構(gòu)建項(xiàng)目失敗的解決方法
這篇文章主要介紹了android studio更新gradle錯(cuò)誤構(gòu)建項(xiàng)目失敗的解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03