二.CALayer及時(shí)間模型
我們都知道UIView是MVC中的View.UIView的職責(zé)在于界面的顯示和界面事件的處理.每一個(gè)View的背后都有一個(gè)layer(可以通過(guò)view.layer進(jìn)行訪問(wèn)),layer是用于界面顯示的.CALayer屬于QuartzCore框架,非常重要,但并沒(méi)有想象中的那么好理解.我們通常操作的用于顯示的Layer在Core Animation這層的概念中其實(shí)擔(dān)當(dāng)?shù)氖菙?shù)據(jù)模型Model的角色,它并不直接做渲染的工作.關(guān)于Layer,之前從座標(biāo)系的角度分析過(guò),這次則側(cè)重于它的時(shí)間系統(tǒng).
1.Layer的渲染架構(gòu)
Layer也和View一樣存在著一個(gè)層級(jí)樹(shù)狀結(jié)構(gòu),稱之為圖層樹(shù)(Layer Tree),直接創(chuàng)建的或者通過(guò)UIView獲得的(view.layer)用于顯示的圖層樹(shù),稱之為模型樹(shù)(Model Tree),模型樹(shù)的背后還存在兩份圖層樹(shù)的拷貝,一個(gè)是呈現(xiàn)樹(shù)(Presentation Tree),一個(gè)是渲染樹(shù)(Render Tree). 呈現(xiàn)樹(shù)可以通過(guò)普通layer(其實(shí)就是模型樹(shù))的layer.presentationLayer獲得,而模型樹(shù)則可以通過(guò)modelLayer屬性獲得(詳情文檔).模型樹(shù)的屬性在其被修改的時(shí)候就變成了新的值,這個(gè)是可以用代碼直接操控的部分;呈現(xiàn)樹(shù)的屬性值和動(dòng)畫(huà)運(yùn)行過(guò)程中界面上看到的是一致的.而渲染樹(shù)是私有的,你無(wú)法訪問(wèn)到,渲染樹(shù)是對(duì)呈現(xiàn)樹(shù)的數(shù)據(jù)進(jìn)行渲染,為了不阻塞主線程,渲染的過(guò)程是在單獨(dú)的進(jìn)程或線程中進(jìn)行的,所以你會(huì)發(fā)現(xiàn)Animation的動(dòng)畫(huà)并不會(huì)阻塞主線程.
2.事務(wù)管理
CALayer的那些可用于動(dòng)畫(huà)的(Animatable)屬性,稱之為Animatable Properties,這里有一份詳情的列表,羅列了所有的CALayer Animatable Properties. 如果一個(gè)Layer對(duì)象存在對(duì)應(yīng)著的View,則稱這個(gè)Layer是一個(gè)Root Layer,非Root Layer一般都是通過(guò)CALayer或其子類直接創(chuàng)建的.下面的subLayer就是一個(gè)典型的非Root Layer,它沒(méi)有對(duì)應(yīng)的View對(duì)象關(guān)聯(lián)著.
subLayer = [[CALayeralloc]init];
subLayer.fra me=CGRectMake(0,0,300,300);
subLayer.backgroundColor=[[UIColorredColor]CGColor];
[self.view.layeraddSublayer:subLayer];
所有的非Root Layer在設(shè)置Animatable Properties的時(shí)候都存在著隱式動(dòng)畫(huà),默認(rèn)的duration是0.25秒.
subLayer.position=CGPointMake(300,400);
像上面這段代碼當(dāng)下一個(gè)RunLoop開(kāi)始的時(shí)候并不是直接將subLayer的position變成(300,400)的,而是有個(gè)移動(dòng)的動(dòng)畫(huà)進(jìn)行過(guò)渡完成的.
任何Layer的animatable屬性的設(shè)置都應(yīng)該屬于某個(gè)CA事務(wù)(CATransaction),事務(wù)的作用是為了保證多個(gè)animatable屬性的變化同時(shí)進(jìn)行,不管是同一個(gè)layer還是不同的layer之間的.CATransaction也分兩類,顯式的和隱式的,當(dāng)在某次RunLoop中設(shè)置一個(gè)animatable屬性的時(shí)候,如果發(fā)現(xiàn)當(dāng)前沒(méi)有事務(wù),則會(huì)自動(dòng)創(chuàng)建一個(gè)CA事務(wù),在線程的下個(gè)RunLoop開(kāi)始時(shí)自動(dòng)commit這個(gè)事務(wù),如果在沒(méi)有RunLoop的地方設(shè)置layer的animatable屬性,則必須使用顯式的事務(wù).
顯式事務(wù)的使用如下:
[CATransactionbegin];...[CATransactioncommit];
事務(wù)可以嵌套.當(dāng)事務(wù)嵌套時(shí)候,只有當(dāng)最外層的事務(wù)commit了之后,整個(gè)動(dòng)畫(huà)才開(kāi)始.
可以通過(guò)CATransaction來(lái)設(shè)置一個(gè)事務(wù)級(jí)別的動(dòng)畫(huà)屬性,覆蓋隱式動(dòng)畫(huà)的相關(guān)屬性,比如覆蓋隱式動(dòng)畫(huà)的duration,timingFunction.如果是顯式動(dòng)畫(huà)沒(méi)有設(shè)置duration或者timingFunction,那么CA事務(wù)設(shè)置的這些參數(shù)也會(huì)對(duì)這個(gè)顯式動(dòng)畫(huà)起作用.
還可以設(shè)置completionBlock,當(dāng)當(dāng)前CATransaction的所有動(dòng)畫(huà)執(zhí)行結(jié)束后, completionBlock會(huì)被調(diào)用.
3.時(shí)間系統(tǒng)
CALayer實(shí)現(xiàn)了CAMediaTiming協(xié)議. CALayer通過(guò)CAMediaTiming協(xié)議實(shí)現(xiàn)了一個(gè)有層級(jí)關(guān)系的時(shí)間系統(tǒng).除了CALayer,CAAnimation也采納了此協(xié)議,用來(lái)實(shí)現(xiàn)動(dòng)畫(huà)的時(shí)間系統(tǒng).
在CA中,有一個(gè)Absolute Time(絕對(duì)時(shí)間)的概念,可以通過(guò)CACurrentMediaTime()獲得,其實(shí)這個(gè)絕對(duì)時(shí)間就是將mach_absolute_time()轉(zhuǎn)換成秒后的值.這個(gè)時(shí)間和系統(tǒng)的uptime有關(guān),系統(tǒng)重啟后CACurrentMediaTime()會(huì)被重置.
就和座標(biāo)存在相對(duì)座標(biāo)一樣,不同的實(shí)現(xiàn)了CAMediaTiming協(xié)議的存在層級(jí)關(guān)系的對(duì)象也存在相對(duì)時(shí)間,經(jīng)常需要進(jìn)行時(shí)間的轉(zhuǎn)換,CALayer提供了兩個(gè)時(shí)間轉(zhuǎn)換的方法:
-(CFTimeInterval)convertTime:(CFTimeInterval)tfromLayer:(CALayer)l;
-(CFTimeInterval)convertTime:(CFTimeInterval)ttoLayer:(CALayer)l;
現(xiàn)在來(lái)重點(diǎn)研究CAMediaTiming協(xié)議中幾個(gè)重要的屬性.
beginTime
無(wú)論是圖層還是動(dòng)畫(huà),都有一個(gè)時(shí)間線Timeline的概念,他們的beginTime是相對(duì)于父級(jí)對(duì)象的開(kāi)始時(shí)間. 雖然蘋(píng)果的文檔中沒(méi)有指明,但是通過(guò)代碼測(cè)試可以發(fā)現(xiàn),默認(rèn)情況下所有的CALayer圖層的時(shí)間線都是一致的,他們的beginTime都是0,絕對(duì)時(shí)間轉(zhuǎn)換到當(dāng)前Layer中的時(shí)間大小就是絕對(duì)時(shí)間的大小.所以對(duì)于圖層而言,雖然創(chuàng)建有先后,但是他們的時(shí)間線都是一致的(只要不主動(dòng)去修改某個(gè)圖層的beginTime),所以我們可以想象成所有的圖層默認(rèn)都是從系統(tǒng)重啟后開(kāi)始了他們的時(shí)間線的計(jì)時(shí).
但是動(dòng)畫(huà)的時(shí)間線的情況就不同了,當(dāng)一個(gè)動(dòng)畫(huà)創(chuàng)建好,被加入到某個(gè)Layer的時(shí)候,會(huì)先被拷貝一份出來(lái)用于加入當(dāng)前的圖層,在CA事務(wù)被提交的時(shí)候,如果圖層中的動(dòng)畫(huà)的beginTime為0,則beginTime會(huì)被設(shè)定為當(dāng)前圖層的當(dāng)前時(shí)間,使得動(dòng)畫(huà)立即開(kāi)始.如果你想某個(gè)直接加入圖層的動(dòng)畫(huà)稍后執(zhí)行,可以通過(guò)手動(dòng)設(shè)置這個(gè)動(dòng)畫(huà)的beginTime,但需要注意的是這個(gè)beginTime需要為 CACurrentMediaTime()+延遲的秒數(shù),因?yàn)閎eginTime是指其父級(jí)對(duì)象的時(shí)間線上的某個(gè)時(shí)間,這個(gè)時(shí)候動(dòng)畫(huà)的父級(jí)對(duì)象為加入的這個(gè)圖層,圖層當(dāng)前的時(shí)間其實(shí)為[layer convertTime:CACurrentMediaTime() fromLayer:nil],其實(shí)就等于CACurrentMediaTime(),那么再在這個(gè)layer的時(shí)間線上往后延遲一定的秒數(shù)便得到上面的那個(gè)結(jié)果.
timeOffset
這個(gè)timeOffset可能是這幾個(gè)屬性中比較難理解的一個(gè),官方的文檔也沒(méi)有講的很清楚. local time也分成兩種一種是active local time 一種是basic local time.
timeOffset則是active local time的偏移量.
你將一個(gè)動(dòng)畫(huà)看作一個(gè)環(huán),timeOffset改變的其實(shí)是動(dòng)畫(huà)在環(huán)內(nèi)的起點(diǎn),比如一個(gè)duration為5秒的動(dòng)畫(huà),將timeOffset設(shè)置為2(或者7,模5為2),那么動(dòng)畫(huà)的運(yùn)行則是從原來(lái)的2秒開(kāi)始到5秒,接著再0秒到2秒,完成一次動(dòng)畫(huà).
speed
speed屬性用于設(shè)置當(dāng)前對(duì)象的時(shí)間流相對(duì)于父級(jí)對(duì)象時(shí)間流的流逝速度,比如一個(gè)動(dòng)畫(huà)beginTime是0,但是speed是2,那么這個(gè)動(dòng)畫(huà)的1秒處相當(dāng)于父級(jí)對(duì)象時(shí)間流中的2秒處. speed越大則說(shuō)明時(shí)間流逝速度越快,那動(dòng)畫(huà)也就越快.比如一個(gè)speed為2的layer其所有的父輩的speed都是1,它有一個(gè)subLayer,speed也為2,那么一個(gè)8秒的動(dòng)畫(huà)在這個(gè)運(yùn)行于這個(gè)subLayer只需2秒(8 / (2 * 2)).所以speed有疊加的效果.
fillMode
fillMode的作用就是決定當(dāng)前對(duì)象過(guò)了非active時(shí)間段的行為. 比如動(dòng)畫(huà)開(kāi)始之前,動(dòng)畫(huà)結(jié)束之后。如果是一個(gè)動(dòng)畫(huà)CAAnimation,則需要將其removedOnCompletion設(shè)置為NO,要不然fillMode不起作用. 下面來(lái)講各個(gè)fillMode的意義
kCAFillModeRemoved這個(gè)是默認(rèn)值,也就是說(shuō)當(dāng)動(dòng)畫(huà)開(kāi)始前和動(dòng)畫(huà)結(jié)束后,動(dòng)畫(huà)對(duì)layer都沒(méi)有影響,動(dòng)畫(huà)結(jié)束后,layer會(huì)恢復(fù)到之前的狀態(tài)
kCAFillModeForwards當(dāng)動(dòng)畫(huà)結(jié)束后,layer會(huì)一直保持著動(dòng)畫(huà)最后的狀態(tài)
kCAFillModeBackwards這個(gè)和kCAFillModeForwards是相對(duì)的,就是在動(dòng)畫(huà)開(kāi)始前,你只要將動(dòng)畫(huà)加入了一個(gè)layer,layer便立即進(jìn)入動(dòng)畫(huà)的初始狀態(tài)并等待動(dòng)畫(huà)開(kāi)始.你可以這樣設(shè)定測(cè)試代碼,將一個(gè)動(dòng)畫(huà)加入一個(gè)layer的時(shí)候延遲5秒執(zhí)行.然后就會(huì)發(fā)現(xiàn)在動(dòng)畫(huà)沒(méi)有開(kāi)始的時(shí)候,只要?jiǎng)赢?huà)被加入了layer,layer便處于動(dòng)畫(huà)初始狀態(tài)
kCAFillModeBoth理解了上面兩個(gè),這個(gè)就很好理解了,這個(gè)其實(shí)就是上面兩個(gè)的合成.動(dòng)畫(huà)加入后開(kāi)始之前,layer便處于動(dòng)畫(huà)初始狀態(tài),動(dòng)畫(huà)結(jié)束后layer保持動(dòng)畫(huà)最后的狀態(tài).
其他的一些參數(shù)都是比較容易理解的.
- (void)pauseLayer:(CALayer*)layer
{
CFTimeInterval curTime =CACurrentMediaTime();
CFTimeInterval pauseTime = [layer convertTime:curTime fromLayer:nil];
layer.timeOffset= pauseTime;
layer.speed=0;
}
- (void)resumeLayer:(CALayer*) layer
{
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed=1.0;
layer.timeOffset=0.0;
layer.beginTime=0.0;
CFTimeInterval timeSincePause =
[layer convertTime:CACurrentMediaTime()fromLayer:nil] - pausedTime;
layer.beginTime= timeSincePause;
}