這篇文章不會教你們如何實現一個具體的動畫效果,我會從動畫的本質出發,來講說 iOS 動畫的原理與實現方式。html
動畫,顧名思義,就是能「動」的畫。
人的眼睛對圖像有短暫的記憶效應,因此當眼睛看到多張圖片連續快速的切換時,就會被認爲是一段連續播放的動畫了。ios
好比,中國古代的「走馬燈」,就是用的這個原理。
有些人還會在一個本子每頁上手繪一些漫畫,當快速翻頁的時候,也會看到動畫的效果,好比:git
動畫是由一張張圖片組成的,在計算機中,咱們稱每一張圖片爲 一幀畫面 。github
若是咱們想實現這麼一個動畫:一個水杯放在桌子的左邊,移動到右邊,那麼咱們實際操做的,只是水杯。
因此動畫的實現,只是對運動變化了的部分的處理。網絡
相似於上面提到的手繪翻頁方式,咱們能夠將這個水杯在每幀畫面中的位置一一找出來,這樣實現動畫的方式就叫做 逐幀動畫,咱們須要處理動畫中的每一幀。app
咱們通常在計算機上用 FPS ( Frames Per Second) ,即 每秒的幀數 來表示動畫的刷新速度,基於屏幕的刷新率等其餘緣由,在計算機上通常採用 60 FPS。
若是運動變化幅度較緩,減半到 30 FPS 時,咱們肉眼也是可接受的。
較低的 FPS 會讓咱們有「卡頓」的感受。框架
逐幀動畫是最直接的,但要處理的幀數太多,因此實現過程是會麻煩。ide
計算機的工做就是來完成重複單調的工做的,因此,有些工做是能夠考慮讓計算機來完成的。函數
上面的例子,能夠變成一個涉及數學和物理的問題:一個杯子初始位置在左邊,n秒後勻速運動到右邊,那麼在每 1/60 秒的時候,這個杯子的位置顯然是能夠計算出來的了。
因此,咱們其實只須要指定一些 關鍵 信息就能讓計算機本身計算出每一幀杯子的位置了:性能
這種方式就稱之爲 關鍵幀動畫。即咱們只須要給定幾個關鍵幀的畫面信息,關鍵幀與關鍵幀之間的過渡幀都將由計算機自動生成。
這裏說的 關鍵幀動畫,是指的廣義上的一種動畫製做方式,並不只指
CAKeyframeAnimation
,CABasicAnimation
的實現方式也屬於 關鍵幀動畫
說完廣義上的動畫,就能夠來講說 iOS 的動畫了。
先來講說動畫的本質。
繼續用上面的簡單例子:一個 UIView
從 (0,0) 勻速移動到 (100,0)的動畫,動畫總時間是0.25秒。
假設咱們基於 60 FPS 來顯示動畫,那麼在0.25秒內就應該有15幀畫面,在每幀畫面中,這個 UIView
的 x座標,每次應移動 100/15 的距離。
若是咱們每隔 0.25/15 秒刷新一次UIView
的 x座標,那麼就能實現這個動畫效果了。
對於 x座標而言,每幀的位置就能夠經過一個基於時間變化量的函數來求得:x=f(t) 。
因此,一個動畫的本質,就是動畫對象(這裏是 UIView
)的狀態,基於時間變化的反應了。
簡單說,就是給定任意一個時刻,若是你都能獲得這個動畫對象的位置和、形狀等等屬性,你就能實現這個動畫了。
屬性值的變化,既多是位置、透明度、旋轉角度等的變化,也包括形狀的改變,好比從一條直線變化成一個圓圈,目標就是要獲得變化過程當中特定時刻的中間態。
咱們也可將 iOS 的動畫分爲兩大類:
簡單的說,要實現逐幀的方式,就是須要 週期性 的調用 繪製 方法,繪製每幀的動畫對象。
這裏說的 繪製,不光是指覆寫 UIView
的 - drawRect:
的方法來手動重繪視圖,也包括修改 UIView
它的屬性,好比位置、顏色等。
iOS 的動畫都是基於
CALayer
的,iOS 的UIView
背後都有一個對應的CALayer
。對UIView
的修改實際上都是對背後CALayer
的修改。
但若是在逐幀繪製的方法中修改了一個自建的CALayer
,這個CALayer
不是對應某個UIView
的,需注意系統的 隱式動畫 的影響,後面會提到這點。
而 週期性,就須要一個定時器來完成了,即 CADisplayLink
。CADisplayLink
與 NSTimer
比較相似,能夠週期性的調用指定的方法。
之因此用 CADisplayLink
,是由於它是基於屏幕刷新率的,即屏幕每次刷新時就會觸發調用。
iPhone 的屏幕刷新率是 60 FPS。
若是繪製過程過於複雜,不能在屏幕刷新一幀的時間內完成,能夠考慮改成每隔一幀繪製,至關因而 30 FPS的刷新率。
否則可能會使動畫不連貫,有卡頓感。
用逐幀方法繪製的原理不是很麻煩,麻煩的是繪製過程。
對於一個複雜動畫,你可能須要運用各類物理、幾何知識去計算視圖中間狀態的信息。
好比要實現一條直線捲曲變化爲一個圓的動畫,你就須要計算出中間態的曲線的彎曲程度和位置。
著名的 facebook 的 pop 動畫框架,就是使用 CADisplayLink
這種逐幀繪製的方式實現的。
採用關鍵幀的方式來實現動畫,要講的內容相對逐幀的方式就多的多了。
仍是用 UIView
移動的簡單例子。
這裏面有兩個關鍵幀,起始幀和結束幀,除此以外還有2個關鍵信息:
座標 信息是 UIView
的一個屬性(實際是對應到 CALayer
的屬性),在動畫實現裏,咱們只須要指定起始和結束的兩個關鍵值就夠了,中間的過渡值都有系統自動生成。
這裏出現了兩種值,一個是咱們設定的,一個是系統生成的,因此要先在這裏插入一個 模型層 和 展示層 的概念了
CALayer
的同一個屬性值,會分別保存在模型層 modelLayer ,和展示層 presentationLayer 中。當咱們修改屬性值時,是修改的模型層的數值,動畫時系統根據模型層的變化,生成的過渡值,是保存在展示層中的。
在
CALayer
的對象裏能直接訪問到這兩層的信息。
而CALayer
的底層實現實際不止這兩層,但咱們如今討論動畫的時候,能夠只關心這兩層。
在整個動畫過程當中,呈現出來的過程是這樣的:
用一段代碼來解釋下動畫過程。
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; view.backgroundColor = [UIColor redColor]; [self.view addSubview:view]; CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(50, 0)]; animation.toValue = [NSValue valueWithCGPoint:CGPointMake(150, 0)]; [view.layer addAnimation:animation forKey:nil]; // view.frame = CGRectOffset(view.frame, 100, 0);
你會發現動畫結束後,view
又跳回了原來的位置,這是由於最後一行代碼註釋了,而這行代碼的功能就是實現第4步,將模型層的值修改成動畫結束時的值。
代碼中的 CABasicAnimation
就是真正的動畫實現部分,也就是設定關鍵幀信息的地方。
將動畫加入 CALayer
的代碼定義爲:
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key
接受的類型是 CAAnimation
類型,有下面這些子類:
CABasicAnimation
,可設定起始結束兩個關鍵幀的信息。CAKeyframeAnimation
,除首尾外,還可添加多箇中間關鍵點。CAAnimationGroup
,可組合多個動畫,由於上面兩種動畫一次只能設置一個屬性值。CATransition
,圖層過渡動畫,默認是淡入。好比修改一個 CALayer
的背景色時,是從初始色慢慢淡入過渡到結束色。CIFilter
濾鏡作過渡效果,一些開源 UIViewController
的過渡動畫使用了這種方式。動畫中,除了屬性值外,咱們還設置了兩個和時間有關的信息:動畫時間0.25秒,運動方式是勻速運動。
動畫持續時間很簡單,是經過 CAAnimation
遵照的 CAMediaTiming
協議設定的。
勻速運動是經過設置 CAAnimation
的 timingFunction
實現的,這是一個 CAMediaTimingFunction
類的對象。
以前已經說到,動畫過程實際是一個時間的函數,橫座標是時間的變化值,縱座標是動畫屬性的變化量。那麼咱們就能夠在一個直角座標系中,經過做圖來畫出這個函數。好比勻速運動的圖形,就是一條經過原點的直線。
因此這個類的功能就是畫出一條曲線,來表示時間和屬性變化之間的關係。而畫圖的方法,是使用的是畫貝葉斯曲線的方法。
系統提供了幾個經常使用的函數,好比 kCAMediaTimingFunctionLinear
就是勻速運動;kCAMediaTimingFunctionEaseInEaseOut
就是通常系統動畫的默認值,漸入漸出,即在動畫開始和結束的時候速度稍慢些。
上面的過程,咱們是 顯式 的向一個 CALayer
添加了一個動畫,因此這種方式叫作 顯式動畫。
對應的,還有 隱式動畫,即系統自動添加上的動畫。
CALayer *layer = [CALayer layer]; layer.backgroundColor = [UIColor greenColor].CGColor; layer.frame = CGRectMake(0, 0, 100, 100); [self.view.layer addSublayer:layer]; layer.frame = CGRectOffset(layer.frame, 100, 0);
這段代碼裏,咱們沒有添加 CAAnimation
動畫,但 layer 不是直接變化到新的位置,而是有一個動畫效果。
這就是 隱式動畫 的效果。
當咱們改變 CALayer
的一個可動畫的屬性值時,就會觸發系統的隱式動畫。
可動畫的屬性值,能夠在 CALayer
的文檔中找到,屬性說明中標有 Animatable 的,就是可自動添加動畫的屬性。
可是,有一個例外,對於 UIView
背後對應的 CALayer
,系統關閉了隱式動畫,因此當咱們直接修改 UIView
或者是其底層的 CALayer
時,變化是直接生效的,沒有動畫效果。
因此當咱們在逐幀方式生成動畫時,是能夠直接修改
UIView
或者是其底層的CALayer
的信息。
可是若是修改的是一個自建的單獨CALayer
時,幀與幀之間的變化仍是會觸發系統的默認隱式動畫,這個時候就須要咱們來手動關閉隱式動畫。
當快速動畫的時候不會察覺到這點,但這明顯會帶來性能上的浪費。
隱式動畫所作的事情和顯示動畫是同樣的,咱們設置的屬性值都是模型層的數值,而系統會自動添加屬性對應的 CAAnimation
動畫到 CALayer
上。
UIView
有一系列的animateWithDuration
動畫方法,在這些方法中UIView
會恢復隱式動畫,因此在動畫的 block 中修改屬性時,又會觸發隱式動畫。
那麼系統是若是知道對一個屬性應該添加哪一種動畫呢,這就須要讓 CAAction
協議登場了。
當修改一個 CALayer
的屬性時,它會經過 - actionForKey:
來查詢這個屬性對應的 action,而 key 就是對應的屬性名稱。CAAnimation
遵照 CAAction
協議,返回的 action 實際上是個 CAAnimation
動畫。
也就是說, CALayer
經過 - actionForKey:
來查詢某個屬性被修改時,須要調用哪一個動畫去展示這個變化。
通常默認返回的是 CABasicAnimation
,默認動畫時間 0.25秒,時間函數爲漸入漸出 kCAMediaTimingFunctionEaseInEaseOut。
- actionForKey:
查詢 action 的步驟有4步,在這個方法中有詳細的說明。
其中一種方式就是經過CALayer
的 delegate 返回 action。而對於UIView
背後對應的CALayer
,其代理就是它對應的UIView
,UIView
就是用這種方式關閉了隱式動畫。
建立動畫事務的目的是爲了操做的原子性,保證動畫的全部修改能同時生效。CATransaction
就是動畫事務的操做類。
在建立隱式動畫的時候,系統也會隱式的建立一個動畫事務,以保證全部的動畫能同時進行。
除此以外,還能夠顯式的建立一個事務。
顯式事務中能夠定義事務中全部動畫的運行時間和時間函數,此外,還有這個方法 + (void)setDisableActions:(BOOL)flag
能顯式的關閉這個事務中的 action 查詢操做。
關閉了查詢也就是關閉了動畫效果,屬性值的變化就會當即生效,而沒有動畫效果了:
[CATransaction begin]; [CATransaction setDisableActions:YES]; ///... layer.frame = CGRectOffset(layer.frame, 100, 0); ///... [CATransaction commit];
注意別把 CATransaction 和 CATransition 搞混了,一個單詞是 transaction 事務,另外一個是 transition 轉變。
關鍵幀動畫的實現方式,只須要修改某個屬性值就能夠了,簡單方便,但涉及的深層次內容較多,須要更多的理解和練習。
採用逐幀動畫的實現方式,實現原理簡單,但繪製動畫的過程要複雜。若是動畫過程處理的事情較多,也會帶來較大的開銷,就有可能形成動畫幀數的降低,出現卡頓的現象,所以須要較多的測試和調試。
動畫繪製的過程當中,會要求較多的數學、物理等知識來計算中間態的數據。
但這兩種方式也不是絕對分離開的。
關鍵幀動畫實現方式,通常只能對系統實現了可動畫的屬性作動畫處理,但其實也是容許實現自定義屬性的動畫處理的。
這就須要本身來實現系統中自動計算過渡幀的操做了,也就是逐幀實現動畫的方式了。
實現自定義屬性的動畫能夠參考這篇文章: Layer 中自定義屬性的動畫
對於 iOS 系統提供的動畫方法,上面只是從總體的角度做了一個全面的整理,還有不少細節內容沒有寫出來,好比 CALayer
的三維變換、CAKeyframeAnimation
的延路徑動畫,CAMediaTiming
的時間控制,等等。感興趣的話,能夠再看看這些內容: