前言
本文关炼,我們將深入CALayer內(nèi)部,通過簡單的CABasicAnimation動畫來探究CALayer的兩個非常重要的屬性:presentationLayer和modelLayer展哭。
從一個改變位置的動畫開始
我們可以使用CABasicAnimation來實現(xiàn)各種動畫相艇,假如我們想讓一個視圖從一個位置動畫地移動到另一個位置堕油,我們的代碼可能會這樣寫:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIView * view = [[UIView alloc] initWithFrame:CGRectMake(80, 80, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
// 動畫開始
CABasicAnimation * animation = [CABasicAnimation animation];
animation.keyPath = @"position";
animation.duration = 2;
animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(80, 80)];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 300)];
[view.layer addAnimation:animation forKey:nil];
}
也就是指定了動畫的四要素:
- 動什么(keyPath)
- 動多久(duration)
- 從什么樣子開始(fromValue)
- 動到什么樣子(toValue)
這樣CABasicAnimation就能在duration內(nèi)對keyPath指定的屬性從fromValue到toValue之間進行插值封救。然后我們將動畫施加于view的layer,這樣它就會按照我們的四要素進行動畫了杏慰。
我們運行一下將會有如下效果:
注意:動畫結(jié)束后测柠,視圖又回去了?
瞎子和瘸子
從前有一個瞎子和一個瘸子缘滥,瘸子看得見路轰胁,但是不能自己走,瞎子能自己走朝扼,但是他看不見路赃阀。于是他們想到了一個辦法:瞎子背著瘸子,這樣瘸子就指揮瞎子如何走路擎颖,瞎子負責(zé)走路榛斯,每走一步,瞎子都會停下來聽從瘸子的指揮搂捧,這樣他們就不緊不慢的走向了目的地驮俗。
在CALayer內(nèi)部也有一個瞎子和一個瘸子:presentationLayer(以下簡稱P)和modelLayer(以下簡稱M)。presentationLayer負責(zé)走路(繪制內(nèi)容)允跑,而modelLayer負責(zé)看路(如何繪制)王凑。
P有這樣的特點:
1、我們看到的一切聋丝,都是P的內(nèi)容荤崇;
2、P只在下次屏幕刷新時才會進行繪制潮针。
M有這樣的特點:
1、我們我們對CALayer的各種繪圖屬性進行賦值和訪問實際上都是訪問的M的屬性倚喂,比如bounds每篷、backgroundColor瓣戚、position等;
2焦读、對這些屬性進行賦值子库,不會影響P,也就是不會影響繪制內(nèi)容矗晃。
你可以把M理解成一個隱身的家伙仑嗅,只有P才能感知它的存在。
那么為什么我們對M的屬性進行賦值张症,“與此同時”視圖的顯示狀態(tài)就會發(fā)生改變呢仓技?
如果我們用t0表示我們對屬性進行賦值的時刻,t1表示下次屏幕刷新的時刻俗他,t0到t1之間的間隔相當(dāng)短(小于1/60秒)脖捻,我們的人眼根本察覺不到這樣的間隔時間存在,所以我們的感覺就是:賦值和界面繪制是同時進行的兆衅。然而實際上地沮,賦值和界面繪制之間有一個t1-t0的時間間隔。
所以總結(jié)以上信息羡亩,我們可以知道P和M是這樣進行交互的:當(dāng)下次屏幕刷新信號到來時(屏幕重繪時)摩疑,P為了繪制內(nèi)容到屏幕上,它會去找M要各種它需要的用來繪制的屬性畏铆,然后用這些屬性的信息來繪制雷袋。
直到屏幕刷新信號到來才進行繪制,這種方式能夠極大提高繪制效率及志∑牛考慮這樣一種情況,如果連續(xù)寫了以下三行代碼:
view.frame = …;
view.backgounrdColor = …;
view.center = …;速侈,
如果每次賦值就會導(dǎo)致屏幕的重繪率寡,這樣就會有三次重繪,也就是GPU會執(zhí)行三次繪制操作倚搬。而如果我們先把繪制信息存起來冶共,當(dāng)需要繪制的時候(屏幕刷新)再用這些信息去重繪,GPU就只會執(zhí)行一次繪制操作每界。如果不能理解這樣做的優(yōu)勢捅僵,我們將會在實戰(zhàn)篇講述如何提高性能的時候再次帶大家了解繪圖機制,也就是你看到的一切究竟是如何被顯示出來的眨层。
上面三行代碼庙楚,只是對M的屬性賦值了而已,沒有任何的繪制操作趴樱,當(dāng)下次屏幕刷新信號到來時馒闷,P才會去找M要這些屬性酪捡,進行一次繪制。執(zhí)行這三次賦值(執(zhí)行三行代碼)所需的時間遠遠小于兩次屏幕刷新的間隔纳账,所以這樣三行代碼是一定能夠在下次繪制開始前執(zhí)行完的逛薇。也就是說,當(dāng)我們寫了這三行代碼后疏虫,視圖的顯示內(nèi)容不會改變?nèi)斡婪#粫淖円淮巍?/p>
如果我們繼續(xù)按照瞎子和瘸子的理論進行下去,P背著M卧秘,如果我們在下次屏幕刷新前(P走下一步之前)對M進行了三次操作:把M移動到點1呢袱,把M移動到點2,把M移動到點3斯议。那么下次屏幕刷新時产捞,P會直接問M“你在哪啊”,M說我在點3呢哼御,你這樣這樣走過來坯临,于是P就屁顛屁顛的按照M的指揮跑到點3去了,它是不會先跑到點1再跑到點2再跑到點3的恋昼。
CAAnimation對presentationLayer的控制
P這個瞎子背著M這個瘸子看靠,他們互幫互助,完成各種顯示的邏輯液肌。每當(dāng)屏幕刷新的時候挟炬,如果沒有其他的信息告訴P它應(yīng)該如何繪制,那么P就只能去問M嗦哆,然后回到M的狀態(tài)谤祖。
這樣一個平衡會在一個CAAnimation被加到CALayer上后會被打破。當(dāng)我們調(diào)用了layer的addAnimation方法后老速,一個CAAnimation(以下簡稱A)就控制了P粥喜,就相當(dāng)于A一腳把M從P身上踹了下來然后自己爬了上去(不要想歪 )。
現(xiàn)在P要如何顯示就完全由A來控制了橘券,因為A被指定了在duration中從fromValue變到toValue额湘。所以在動畫的持續(xù)時間內(nèi),M被丟在了原地旁舰,P則背著A到處跑(畢竟P是個瞎子锋华,根本不知道他腦袋頂上的家伙是誰),每一步該如何走都是A通過插值計算出來的(通過duration箭窜、from毯焕、to就能計算任意時刻P的狀態(tài)了)。
動畫結(jié)束后磺樱,默認情況A就被移除了纳猫,這時候P身上啥也沒有了紧阔。那么在下一次屏幕刷新的時候,沒有其他的信息告訴P它應(yīng)該如何繪制续担,所以它就又開口了“M你在哪里啊”,M說“勞資在這里”(在動畫過程當(dāng)中它就沒被動過活孩,除非你手動設(shè)置了M的值)物遇,“你怎么跑那里去了”,“要你管憾儒,快給我滾過來”(真是一對好CP)询兴。于是P就屁顛屁顛的滾到M那里去了,所以動畫結(jié)束后起趾,你會看到視圖又回去了诗舰。
如果想要P在動畫結(jié)束后就停在當(dāng)前狀態(tài)而不回到M的狀態(tài),我們就需要給A設(shè)置兩個屬性训裆,一個是A.removedOnCompletion = NO;表示動畫結(jié)束后A依然影響著P眶根,另一個是A.fillMode = kCAFillModeForwards,(關(guān)于fillMode到底干了什么我們將在下一章詳細進行講解)边琉,這兩行代碼將會讓A控制住P在動畫結(jié)束后保持不變(不會回到M的狀態(tài))属百,我們一開始的代碼就會寫成這個樣子:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIView * view = [[UIView alloc] initWithFrame:CGRectMake(80, 80, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
CABasicAnimation * animation = [CABasicAnimation animation];
animation.keyPath = @"position";
animation.duration = 2;
animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(80, 80)];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 300)];
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[view.layer addAnimation:animation forKey:nil];
}
模型與顯示的同步
我們可以通過設(shè)置removedOnCompletion = NO以及fillMode來讓動畫結(jié)束后保持狀態(tài),但是此時我們的P和M不同步变姨,我們看到的P是toValue的狀態(tài)族扰,而M則還是自己原來的狀態(tài)。舉個栗子定欧,我們初始化一個view渔呵,它的狀態(tài)為1,我們給它的layer加個動畫砍鸠,from是0扩氢,to是2,設(shè)置fillMode為kCAFillModeForewards睦番,則動畫結(jié)束后P的狀態(tài)是2类茂,M的狀態(tài)是1,這可能會導(dǎo)致一些問題出現(xiàn)托嚣。比如你點P所在的位置點不動巩检,因為響應(yīng)點擊的是M。所以我們應(yīng)該讓P和M同步示启。
在CABasicAnimation的文檔中寫了這樣一句話:如果不設(shè)置toValue兢哭,則CABasicAnimation會從fromValue到M的值之間進行插值。也就是說夫嗓,如果不設(shè)置toValue迟螺,則CABasicAnimation會把M的值作為toValue冲秽,所以我們就可以在加動畫的時候只設(shè)置fromValue,再手動修改M的值到你想要動畫停止的那個狀態(tài)就保持同步了矩父。
我們可以擴展到任意的CAAnimation對象锉桑,比如CAKeyFrameAnimation,都可以通過設(shè)置M的值到動畫結(jié)束的狀態(tài)來保持P和M的同步窍株。
所以我們的代碼可能就會寫成這樣:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIView * view = [[UIView alloc] initWithFrame:CGRectMake(80, 80, 100, 100)];
view.center = CGPointMake(200, 300);
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
CABasicAnimation * animation = [CABasicAnimation animation];
animation.keyPath = @"position";
animation.duration = 2;
animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(80, 80)];
// animation.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 300)];
// animation.removedOnCompletion = NO;
// animation.fillMode = kCAFillModeForwards;
[view.layer addAnimation:animation forKey:nil];
}
當(dāng)我們寫view.center = CGPointMake(200, 300);的時候民轴,只是對M賦值,再次強調(diào)球订,它不會影響P的顯示后裸,而當(dāng)P想要顯示的時候,它已經(jīng)被A控制了冒滩,所以P會從一開始就在80,80那里然后動畫地移動到200,300微驶,不會出現(xiàn)先在200,300那里閃一下的情況。
運行一下开睡,效果就是這樣: