一、引言
在iOS開發中使用動畫時,可以通過設置動畫的duration
、speed
、begintime
、offset
屬性,來設置動畫的時長、速度、起始時間及起始偏移。
用一個簡單的例子來說明各個參數的的作用。動畫很簡單,一個紅色的方塊從左移到右邊。動畫的持續時間是1s,沒有重復,效果如下。
CFTimeInterval currentTime = CACurrentMediaTime();
CFTimeInterval currentTimeInLayer = [self.testLayer convertTime:currentTime fromLayer:nil];
CFTimeInterval addTime = currentTimeInLayer;
anim.beginTime = 0.3 + addTime;
[anim setTimeOffset:0.5];
[anim setSpeed:2];
做修改以后,效果如下:
與上面相比,三處不同
- 動畫的速度是原來的兩倍。
- 點擊開始動畫的按鈕,到開始動畫,有一個延遲。
- 動畫起始時,滑塊的位置為中央,而不是在左邊。
我們已經看到了這些屬性的效果。翻閱文檔,發現begintime
、speed
等屬性是CAMediaTiming
這協議的屬性,并且CALayer
、CAAnimation
都遵守了CAMediaTiming
協議。
那么CAMediaTiming
協議是什么呢?有什么作用呢?
二、層級時間結構
根據文檔,CMediaTiming
協議構建了一個層級的時間系統,并用這個層級的時間系統來協調各個layer、animation的時間。
這個協議被CAAnimation
及CALayer
遵守,每一個遵守協議的的object對應一個time space
。根據object之間的關系,不同的time space
有層級關系。比如Layer A有一個subLayer B,那么Layer A對應的time space
就是layer B對應的time space
的parent time space
。每一個time space
中時間的數值都是根據parent time space
的數值,以及begintime
、speed
等屬性,根據一定的規則來計算的。
為了便于理解層級時間系統,先看下layer在屏幕上的顯示位置是如何確定的,然后做一個類比。
layer層級如上。要確定sublayer1在屏幕上的顯示位置,一共分三步。
- 確定window layer在屏幕位置position1
- 根據position1及view layer的position屬性,確定view layer在屏幕中的位置position2
- 根據position2及sublayer1的position屬性,確定sublayer1在屏幕中的位置position3
與此類似,要確定sub1ayer1中的time,也要分三步。
- 確定window layer中的time1
- 根據time1及view layer的
begintime
、offset
等屬性計算出view layer中的time2 - 根據time2及sublayer1的
begintime
、offset
等屬性計算出sublayer中的time3
和確定layer的位置相比,確定時間有一些復雜,主要提現在下面兩點
- 層級時間系統的構成復雜。
layer tree的每一級都是CALayer,而只要遵守CAMediaTiming
協議,就可以作為層級時間系統的一部分。比如CALayer
、CAAnimation
(及其子類CAAnimationGroup
)都可以作為層級時間系統的一部分。 - 不同層級之間時間轉換規則復雜
計算當前layer的位置時,只需要知道父layer的位置,以及當前layer的position屬性。計算當前層級時間時,不僅需要知道上一個層級的時間,還需要知道當前層級的begintime
、offset
、speed
等屬性。轉換的規則也比較復雜,要經歷兩次轉換。從parent time
到active local time
,再到basic local time
。
三、active local time
這次轉換是為了處理當前層級的object在父層級的的時間線上的位置,以及當前層級和父層級之間時間流逝速度的關系。
和這次轉換相關的屬性有beginTime
、speed
以及timeOffset
-
begin time
子層級相對于父層級的起始時間。也就是父層級的時間經過多久,子層級才開始計算時間。
比如子層級A被加入層級時間系統時,它父層級B的時間是5s,子層級A的begintime
是6s,那么當它父層級的時間變為6s時,子層級才開始計算時間。 -
speed
子層級相對于父層級的時間流逝速度。如果speed是2,那么當父層級的時間增加了10s時,子層級的時間增加了20s(10s的2倍)。 -
timeOffset
為本地時間增加一個偏移。 如果timeOffset
是5s,那么本地時間的起始就是5s。
從parent time
到active local time
有一個公式,可以用來參考。
t = (tp - begin) * speed + offset
四、basic local time
這次轉換是為了處理當前層級的重放(repeat)、以及重放之前是否要倒放(play backward)等操作。
比如當前層級是一個動畫(CAAnimation
遵守CAMediaTiming
協議),duration
是1s,經過第一次轉換之后的active local time
是5.5s。如果動畫的repeatCount
是10,那么經過第二次轉化以后,basic local time
會是0.5s,因此當前是動畫展示一半的狀態。
-
repeatCount
及repeatDuration
當前的層級要重復的次數或重復的時間,兩者不可同時指定。
以動畫為例,如果指定repeatCount
,那么指定了動畫要重復幾次。如果指定了repeatDuration
,那么指定了動畫重復的時間。 -
autoreverses
在重復之前是否要倒放。
五、文首的例子
根據這些知識,可以解釋文章開始時設置參數的效果。
當動畫被加到layer上時,動畫對應的time space
被加到層級時間系統中,是layer對應的time space
的子層級。
- 動畫的速度是原來的兩倍
設置動畫的speed
是2,這樣子動畫中的時間流逝速度時layer中時間流逝速度的2倍。當layer中時間經過0.5s時,動畫中時間已經流逝了1s,動畫已經完成了。(動畫的duration
是1s) - 點擊開始動畫的按鈕,到開始動畫,有一個延遲
我們首先得到了當前layer的時間addtime
,然后把動畫的begintime
設置為addtime+0.3
。這樣子當動畫被加到layer之后0.3s,layer中的時間是addtime+0.3
,此時動畫中的時間才開始計算,之前動畫沒有開始。 - 動畫起始時,滑塊的位置為中央,而不是在左邊
我們設置了動畫的offset
為0.5s。當動畫開始時,動畫對應的time space
的時間是0.5s,對應動畫duration
的一半,即滑塊位置在屏幕中央。
六、更多應用
了解了CAMediaTiming
協議后,可以實現很多動畫的效果。
- 讓某一個layer上的動畫停止
設置layer的speed
為0即可。 - 實現門打開然后關閉的效果
實現一個門打開的動畫,然后把動畫的autoreverses
屬性設置為YES
即可。 - layer上的若干動畫依次延遲啟動
分別設置這些動畫的beginTime
為不同的值即可 - 手動控制動畫的進度
設置動畫的speed
為0,然后改變動畫的offset
即可。
蘋果已經把工具給我們了,可以做出什么樣的產品就看大家的想象力了。