前言
本文要探討的是CoreAnimation框架是如何來控制時間的。
CAMediaTiming協議
動畫所有跟時間相關的屬性(duration, beginTime, repeatCount等)都來自于CAMediaTiming協議,它由CABasicAnimation和CAKeyframeAnimation的父類CAAnimation實現。協議一共定義了8個屬性,通過這8個屬性就能完全地控制動畫時間。每個屬性的文檔只有短短幾句話,當然你也可以通過閱讀這些文檔并且手動進行試驗來進行學習,不過我認為更容易讓人理解的方式是將時間可視化。
可視化的CAMediaTiming協議
為了向你們展示不同的時間相關屬性,包括這個屬性自己單獨使用的效果以及和其他屬性混合使用的效果,我將執行一個從橘黃色到藍色轉換的動畫。下圖展示了從動畫開始到動畫結束的進程(橘色到藍色),每一格代表一秒,時間線上任意一點對應到圖上的顏色就是視圖在這一瞬間的顏色。比如,duration這個屬性將被如下進行可視化展示:
duration設置為1.5秒,所以動畫將耗費1秒加上1秒的一半來從橘色完全變為藍色。
???????圖一. 將duration設為1.5秒
默認地,CAAnimation將會在動畫完成后被移除,這在上面同樣被可視化出來了,一旦動畫到達了結束值,它就會被從layer上移除,所以layer的背景色將會返回到modelLayer的狀態(見上一章:CALayer的模型層與展示層)。在這個可視化例子中,layer本身的背景色是白色,所以你看到的上圖的可視化效果中,在1.5秒后的額外的2.5秒鐘的時間里layer的背景色回到了白色。
beginTime
如果我們將動畫的beginTime加入到可視化效果中就能看到更多的情形。
???????圖二. 將duration設為1.5秒,將開始時間設為1.0秒
將動畫持續時間設為1.5秒,開始時間設為當前時間(CACurrentMediaTime())加上1秒所以動畫將在2.5秒后結束。在動畫被加到layer上之后它將等待1秒然后再開始(相當于動畫延遲時間為1秒)。
fillMode
如果要讓動畫在開始之前(延遲的這段時間內)顯示fromValue的狀態,你可以設置動畫向后填充:設置fillMode為kCAFillModeBackwards。
???????圖三、填充模式可以用來在動畫開始之前顯示fromValue
autoreverses
將使動畫先正常走,完了以后反著從結束值回到起始值(所有動畫屬性都會反過來,比如動畫速度,如果正常的是先快后慢,則反過來后變成先慢后快)。
???????圖四. Autoreverse將使動畫在結束后又回到動畫開始的狀態
repeatCount
相比之下,repeatCount可以讓動畫重復執行兩次(首次動畫結束后再執行一次,正如你下面將看到的)或者任意多次(你甚至可以將重復次數設置為小數,比如設置為1.5,這樣第二次動畫只執行到一半)。一旦動畫到達結束值,它將立即返回到起始值并且重新開始。
???????圖五、repeatCount讓動畫在結束后再次執行
repeatDuration
類似于repeatCount,但是極少會使用。它簡單地在給定的持續時間內重復執行動畫(下面設置為2秒)。如果repeatDuration比動畫持續時間小,那么動畫將提前結束(repeatDuration到達后就結束)
???????圖六. repeatDuration使動畫在給定時間內不停重復播放
這些屬性可以結合到一起使用來實現一些動畫多次重復反向(也可以在制定時間內重復反向)的效果。如下圖所示:
???????圖七. 把幾個屬性結合起來用
speed
這是一個非常有意思的時間相關的屬性。如果把動畫的duration設置為3秒,而speed設置為2,動畫將會在1.5秒結束,因為它以兩倍速在執行。
圖八. Speed設置為2將會使動畫的速度變為2倍所以3秒的動畫將只用1.5秒就能執行完
speed屬性的強大之處來自以下兩個特點:
1、 動畫速度是有層級關系的
2、 CAAnimation并不是唯一的實現了CAMediaTiming協議的類。
動畫速度的分層表示:
一個動畫的speed為1.5,它同時是一個speed為2的動畫組的一個動畫成員,則它將以3倍速度被執行。
CAMediaTiming協議的其他實現
CAAnimation實現了CAMediaTiming協議,然而CoreAnimation最基本的類:CALayer也實現了CAMediaTiming協議。這意味著你可以給一個CALayer設置speed為2,那么所有加到它上面的動畫都將以兩倍速執行。這同樣符合動畫時間層級,比如你把一個speed為3的動畫加到一個speed為0.5的layer上,則這個動畫將以1.5倍速度執行。
控制動畫和layer的速度同樣可以用來暫停動畫,你只需要把speed設為0就行了。結合timeOffset屬性,就可以通過一個外部的控制器(比如一個UISlider)來控制動畫了,我們將在這一章較后的內容中進行講解。
timeOffset
timeOffset這個屬性,一開始看起來挺奇怪的,光看名字的話,看起來應該是一個用來控制動畫時間進程(計算動畫當前狀態)的屬性。下面這個可視化展示了一個持續時間為3秒,動畫時間偏移量為1秒的動畫。
圖9.你可以偏移整個動畫但是動畫還是會走完全部過程。
這個timeOffset可能是這幾個屬性中比較難理解的一個,官方的文檔也沒有講的很清楚。你將一個動畫看作一個環,timeOffset改變的其實是動畫在環內的起點,比如一個duration為5秒的動畫,將timeOffset設置為2,那么動畫的運行則是從原來的2秒開始到5秒,接著再0秒到2秒,完成一次動畫.
這個動畫將從正常動畫(timeOffset為0的狀態)的第一秒開始執行,直到兩秒后它完全變藍,然后它一下子跳回最開始的狀態(橙色)再執行一秒。就像是我們把正常動畫的第一秒給剪下來粘貼到動畫最后一樣。
這個屬性實際上并不會自己單獨使用,而會結合一個暫停動畫(speed=0)一起使用來控制動畫的“當前時間”。
暫停的動畫將會在第一幀卡住,然后通過改變timeOffset來隨意控制動畫進程,因為如上圖所示,動畫的第一幀就是timeOffset指定的那一幀。
舉個例子:比如一個改變位置的動畫,讓一個視圖從(0,0)移動到(100,100),持續時間為1秒。如果先暫停動畫,然后設置timeOffset為0.5,那么首先動畫會卡在“第一幀”,而第一幀由timeOffset決定,也就是動畫正常運作時間進行到0.5秒的那一幀就是“第一幀”,這時候動畫就會停在(50,50)的地方。注意timeOffset是具體的秒數而不是百分比。
更多的動畫時間可視化插圖
控制動畫時間
結合使用speed和timeOffset屬性可以輕松控制一個動畫當前顯示的內容。為了方便起見,我將把動畫持續時間設為1秒。timeOffset/duration這個分數的值表示動畫進行的百分比,把duration設為1的話,在數值上timeOffset就等于動畫進程百分比了。
Slider
我們首先創建一個CABasicAnimation來創建一個改變layer背景顏色的動畫并把它添加到layer上,然后把layer的speed屬性設為0來暫停動畫。
CABasicAnimation *changeColor =
[CABasicAnimation animationWithKeyPath:@"backgroundColor"];
changeColor.fromValue = (id)[UIColor orangeColor].CGColor;
changeColor.toValue = (id)[UIColor blueColor].CGColor;
changeColor.duration = 1.0; // For convenience
[self.myLayer addAnimation:changeColor
forKey:@"Change color"];
self.myLayer.speed = 0.0; // Pause the animation
然后在slider被拖動的action方法中,我們把slider的當前值(默認0到1,剛好也是動畫timeOffset的范圍)設為layer的timeOffset的值。
- (IBAction)sliderChanged:(UISlider *)sender {
self.myLayer.timeOffset = sender.value; // Update "current time"
}
這樣的效果就像我們通過拖動一個slider來改變一個layer的背景顏色。