感謝大家對前幾篇的支持,這一篇,我們一鼓作氣,把整個動畫完成。
慣例,為了方便第一次來的同學,我先貼一下動畫完成的效果圖:
實現階段4時,我們用了一種處理問題的方式,大約是這樣的:
描述問題,直到足夠清晰,
把問題分解成一組小問題,
利用經驗處理可以解決的問題,
經驗無法解決的問題,我們去調研,調研結果會成為我們下次的經驗。
階段5中,我們再應用一下這個方式(有疑惑的同學可以戳第三篇)。
先來看一下階段5的效果圖,
慣例,前幾個階段的動畫我們用灰色快速表示,當前階段使用彩色慢速表示,如圖:
階段5
看上去比階段4還要復雜,別急,我們來描述一下:
一開始圓是扁的,圓裡面有一條粗線,粗線的頂部和圓的頂部連在一起,
圓漸漸恢復原狀,同時粗線漸漸變長,連到了圓的底部,與此同時,粗線的某處出現了兩條線,分別向左下、右下延伸,漸漸連到了圓上。
很粗糙,但基本上描述出了這個階段。
上文中我加黑了部分文字,這些文字很有標志性(有的平台轉載時看不到加黑,可以戳原文查看)
“一開始”,指示出了本階段動畫的初始階段
“漸漸”,指示出了動畫
“同時”、“分別”,指示出了本階段中可以拆分出的子動畫
由此我們可以得到下面的描述:
初始:
圓:扁的
粗線:頂部和圓頂部連著,底部不連著
左下線:看不到
右下線:看不到
動畫:
圓:恢復到正常的形狀
粗線:變長
左下線:出現並變長
右下線:出現並變長
結束:
圓:正常的形狀
粗線:頂點、底點分別與圓的頂點、底點連著
左下線:起點在粗線上,終點在圓上
右下線:起點在粗線上,終點在圓上
和前面的描述相比,這個描述形式化了,雖然還比較粗略,但已經清晰的標明動畫被拆分成了4部分,各部分的初始及結束狀態也有了。
我們給4部分染上不同的顏色看一下:
階段5 多彩版
是不是比之前的清晰多了,
描述問題和分解問題,到這裡我們就完成了。
接下來就是思考動畫的方案了:
圓:階段4中是執行transform.scale.y動畫變扁了,本階段將transform恢復為CATransform3DIdentity就可以了;
粗線:階段4中是從無成長到一定長度,用的stroke方案(想了解stroke方案的同學請戳第二篇),這個階段就是繼續變長,沿用stroke方案就可以了;
左下線、右下線:本質是一樣,從無成長到一定長度,類比粗線可知,可以使用stroke方案;
這些方案都是以前的經驗,階段5可以不用調研了。
接下來就是找關鍵的節點值了:
對於一個動畫來講,關鍵的節點就是初始狀態和結束狀態,前幾篇中我們一起找過了關鍵的節點值,相信大家已經有感覺了,本文我們就不再找了。
我們一起來看一下階段5中特殊的地方:
前文中說到粗線時,我說的是繼續變長
繼續聽上去就有延續的意思,看上去,粗線兩個階段的動畫可以合並成一個。
回憶一下,階段4中粗線的path起點是圓未變形時的頂點,終點是圓未變形時的圓心,strokeEnd是從path起點逐漸stroke到path的終點,如下圖(灰色是path,藍色是stroke,SS、SE是初始,SS'、SE'是結束):
階段4 粗線
結合階段5,我們可以將path的終點修改為圓的底點,這樣一來,就將階段4的strokeEnd修改為從path的起點stroke到path的1/2處,如下圖:
新階段4 粗線
而階段5的strokeEnd就是在階段4的基礎上繼續stroke到path的終點,這樣兩個階段的動畫就合到一起了,如下圖:
階段5 粗線
有的同學可能會說,早知道是這樣,一開始就這樣寫就可以了。
這麼說的是有道理的,有的人習慣這樣,先分階段考慮,再整體看一下各階段,該合並的合並,該修改的修改,方案成形,最後寫代碼,這是個很好的方式。
然而對我來說,階段性的成就感很重要,每當我看到一個階段的動畫在我眼前動起來,感覺都很爽,所以我還是習慣於逐個階段的實現,有需要時重構前面的階段,只要邏輯清晰,重構起來問題不大。
每個人都能找到最適合自己的方式,這本身就是一種樂趣。
好了,階段5我們聊了很多,後面的階段我們就簡要的說一下了。
從完整的效果圖可知,這個動畫是有成功和失敗兩個狀態的,因此我們分開來看。
成功狀態,從階段5到階段6_success:
階段6_success
描述一下:
圓變色
對號漸漸出現(stroke)
失敗狀態,先是從階段3直接到階段6_fail:
階段6_fail
描述一下:
圓變色
歎號的上半部分漸漸出現(stroke)
歎號的下半部分漸漸出現(stroke)
然後從階段6_fail到階段7_fail:
階段7_fail
描述一下:
歎號繞圓心左右晃幾下(rotate)
階段7_fail要簡單的說一下:
在階段6_fail中,歎號被拆分成了上下兩個layer,而在階段7_fail中兩者又是作為一個整體動的,我們要讓它們分別執行動畫麼?
不是的,一個獨立的動畫應該只涉及一個對象,兩個layer有共同的superLayer,讓superLayer執行動畫就可以了,假如superLayer還有其他subLayer,不方便執行動畫,我們在兩個layer和superLayer中間插入一層專門執行動畫的layer就可以了。
到這裡,我們的動畫就完成了,完整代碼請移步GitHub上的OneLoadingAnimation工程。
在結束之前,我們簡單說一下階段1另一種思路(想看看階段1的同學請戳第一篇),這個思路更符合直覺,這個思路是受簡友YouXianMing在第一篇中的評論啟發,感謝。
先回憶一下階段1的樣子:
階段1
描述一下:
圓從不完整漸漸變到完整(stroke)
圓在漸漸旋轉(rotate)
由此我們得出,一個圓同時執行stroke和rotate動畫就可以了,下面是示意代碼
// 不完整的示意代碼 - (void)doStep1 { // 不用自定義layer了 self.arcToCircleLayer = [CAShapeLayer layer]; // stroke動畫 CABasicAnimation *ssAnima = [CABasicAnimation animationWithKeyPath:@"strokeStart"]; CABasicAnimation *seAnima = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; // rotate動畫 CABasicAnimation *rotateAnima = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; // 同時執行 CAAnimationGroup *animation = [CAAnimationGroup animation]; animation.animations = @[ssAnima, seAnima, rotateAnima]; }
是不是比第一篇的實現方式更清晰,
我們也可以看到,問題分解後,局部的優化也比較方便,
這部分的完整代碼我放到了工程的OneLoadingAnimationStep1Another目錄下。
有的同學還記得,我們這是一個簡化的版本,階段4中原動效中圓的不規則變形被我處理成了規則變形,
為了思路不被卡住,我選擇了暫時簡化,完成之後,我們可以再去優化。
為了彌補這個缺憾,我會開一個外篇,專門聊一下圓不規則變形的實現,歡迎大家到時來捧場;
另,有簡友在簡信中提到了Swift,因此我寫了一個Swift版的實現,放在了工程的OneLoadingAnimationCompleteSwift目錄下,由於我的Swift水平不夠,代碼裡還有坑,僅供參考。
本系列的主線到這就完結了,非常感謝大家的捧場!
完整代碼
請參考GitHub上OneLoadingAnimation工程。
本系列的傳送門
一款Loading動畫的實現思路(一)
一款Loading動畫的實現思路(二)
一款Loading動畫的實現思路(三)
一款Loading動畫的實現思路(四·完結篇)
鳴謝及推薦
原動效的設計者 moonjoin
優秀的動效教程 Kitten's 時間膠囊
喵神發起的objc中國的動畫部分,都是很優秀的譯文,衷心為翻譯的同學點贊。
相關鏈接
Core Animation Programming Guide
CALayer Class Reference
CAShapeLayer Class Reference
UIBezierPath Class Reference