我們可以用屬性或者keyframe做很多事情瞎惫。但是有時(shí)候我們需要一起或者按照順序執(zhí)行多個(gè)動(dòng)畫,我們可以使用更高級(jí)的方式來同步這些動(dòng)畫的時(shí)序來將他們鏈接在一起喊衫。我們還可以用其他類型的動(dòng)畫對(duì)象來創(chuàng)建視覺過渡和其他有趣的動(dòng)畫效果
過渡(Transition)動(dòng)畫支持對(duì)layer可視的改變
顧名思義蒸辆,過渡動(dòng)畫對(duì)象可以為layer創(chuàng)建一個(gè)過動(dòng)畫過渡視覺足绅。過渡動(dòng)畫最常見的用途是以協(xié)調(diào)的方式設(shè)置layer的顯示和領(lǐng)一個(gè)layer的消失。與基本動(dòng)畫不一樣笆焰,過渡動(dòng)畫操作的是layer的緩存image來創(chuàng)建通過單獨(dú)更改屬性而無法完成的可視效果劫谅。標(biāo)準(zhǔn)的過渡類型包含顯示,push嚷掠,移動(dòng)或者交叉淡入淡出動(dòng)畫捏检。
要執(zhí)行過渡動(dòng)畫,首先我們需要?jiǎng)?chuàng)建CATransition 對(duì)象叠国,然后將對(duì)象添加到需要過渡需要的圖層中未檩。我們可以使用過渡對(duì)象執(zhí)行要執(zhí)行的過渡類型以及過渡動(dòng)畫的起點(diǎn)和終點(diǎn)。過渡對(duì)象允許我們指定動(dòng)畫時(shí)候要使用的開始和結(jié)束進(jìn)度值粟焊。這些值允許我們?cè)谄渲虚g出開始或者結(jié)束動(dòng)畫冤狡。
UIView * myView2 =[[UIView alloc]initWithFrame:self.bounds];
[self addSubview:myView2];
myView2.backgroundColor = [UIColor yellowColor];
UIView *myView = [[UIView alloc]initWithFrame:self.bounds];
myView.backgroundColor = [UIColor redColor];
CGPoint center = myView.center;
center.x-=self.bounds.size.width;
myView.center = center;
[self addSubview:myView];
CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
transition.duration = 1.0;
// Add the transition animation to both layers
[myView.layer addAnimation:transition forKey:@"transition"];
[myView2.layer addAnimation:transition forKey:@"transition"];
實(shí)現(xiàn)效果
自定義動(dòng)畫時(shí)間
時(shí)間是動(dòng)畫的重要組成部分,通過CoreAnimation我們可以用CAMediaTiming協(xié)議的方法和屬性為動(dòng)畫指定精確的時(shí)間控制项棠。兩個(gè)CoreAnimation類實(shí)現(xiàn)了該協(xié)議悲雳。CAAnimation 類實(shí)現(xiàn)了該協(xié)議,以便我們?cè)趧?dòng)畫對(duì)象中指定計(jì)時(shí)信息香追。CALayer也實(shí)現(xiàn)了該協(xié)議合瓢,方便我們?yōu)殡[式動(dòng)畫配置一些與時(shí)序相關(guān)的功能,不過Calyer包裝這些動(dòng)畫的隱式事務(wù)對(duì)象一般情況下是提供的默認(rèn)時(shí)序信息透典。
當(dāng)我們考慮時(shí)間和動(dòng)畫的時(shí)候晴楔,了解layer動(dòng)畫如何隨著時(shí)間變化很重要。每個(gè)layer都有自己的本地時(shí)間峭咒,用于管理動(dòng)畫計(jì)時(shí)税弃。通常情況下,兩個(gè)不同layer的本地時(shí)間很接近凑队,我們可以給每個(gè)layer指定相同的時(shí)間值则果,用戶可能不會(huì)感覺有任何變化。但是layer的本地時(shí)間可以通過其父類或者自己的時(shí)序參數(shù)進(jìn)行修改。比如:更改layer的速度屬性可能導(dǎo)致該layer以及sublayer 上的動(dòng)畫持續(xù)時(shí)間按照比例更改西壮。
為了幫助我們確保時(shí)間值是恰當(dāng)?shù)囊旁觯珻alyer類定義了convertTime:fromLayer:和convertTime:toLayer:方法。我們可以通過這些方法將固定時(shí)間值轉(zhuǎn)換為layer的本地時(shí)間款青,或者將時(shí)間值從一個(gè)layer轉(zhuǎn)換到領(lǐng)一個(gè)layer做修。這些方法會(huì)影響layer圖層的本地時(shí)間,這些方法會(huì)返回與其他圖層一起使用的值可都。
CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];
一旦layer的本地時(shí)間有值后缓待,我們可以使用改之更新動(dòng)畫對(duì)象或layer與時(shí)序有關(guān)的屬性。使用這些計(jì)時(shí)屬性渠牲,我們可能實(shí)現(xiàn)一些有趣的動(dòng)畫旋炒,如下:
- 使用beginTime 屬性設(shè)置動(dòng)畫的開始時(shí)間。通常签杈,動(dòng)畫在下一個(gè)更新周期開始瘫镇。但是我們可以使用beginTime參數(shù)將動(dòng)畫開始時(shí)間延遲幾秒執(zhí)行。將兩個(gè)動(dòng)畫連接在一起的方法是將一個(gè)動(dòng)畫的開始時(shí)間設(shè)置為與另一個(gè)動(dòng)畫結(jié)束時(shí)間想匹配答姥。
如果我們延遲動(dòng)畫開始時(shí)間铣除,我們還需要將fillMode 屬性設(shè)置成kCAFillModeBackwards。即使layer對(duì)象在layer tree中包含了一個(gè)不同的起始值鹦付,該模式也會(huì)使用layer展示動(dòng)畫的起始值尚粘。如果不適用此模式,我們可能看到動(dòng)畫在執(zhí)行之前跳轉(zhuǎn)到了最終值了敲长。
autoreverses 屬性使動(dòng)畫在指定的持續(xù)時(shí)間內(nèi)執(zhí)行郎嫁,然后返回到動(dòng)畫的起始位置。我們可以將此屬性與repeatCount屬性組合使用祈噪,使動(dòng)畫循環(huán)執(zhí)行泽铛。如果我們將repeatCount設(shè)置為1,動(dòng)畫停在起始位置辑鲤,1.5停止在結(jié)束位置盔腔。
用timeOffset屬性與組動(dòng)畫一起使用可以在稍后的時(shí)間啟動(dòng)某些動(dòng)畫。
據(jù)我估計(jì)大家看到這里也是一臉懵逼月褥。不如上代碼大家看的更明確
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
MYLayer * layer = [MYLayer layer];
layer.position= CGPointMake(150, 150);
layer.bounds = self.bounds;
layer.backgroundColor = [UIColor redColor].CGColor;
[self.layer addSublayer:layer];
self.anLayer = layer;
CABasicAnimation* theAnim = [CABasicAnimation animationWithKeyPath:@"position"];
theAnim.fromValue = [NSValue valueWithCGPoint:self.layer.position];
theAnim.toValue = [NSValue valueWithCGPoint:CGPointMake(0, 0 )];
theAnim.duration = 5;
[layer addAnimation:theAnim forKey:@"AnimateFrame"];
UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
button.backgroundColor = [UIColor blueColor];
button.frame =CGRectMake(0, 0, 100, 50);
[button addTarget:self action:@selector(button:) forControlEvents:UIControlEventTouchDown];
[self addSubview:button];
}
return self;
}
-(void)button:(UIButton *)button{
static BOOL ispause = YES;
if (ispause) {
ispause = NO;
[self pauseLayer:self.layer];
}else{
ispause = YES;
[self resumeLayer:self.layer];
}
}
-(void)pauseLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
-(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;
}
convertTime:fromLayer: 這個(gè)函數(shù)將layer的本地時(shí)間更改為系統(tǒng)的時(shí)間弛随。
layer的speed 設(shè)置為0 代表不執(zhí)行動(dòng)畫了
layer.timeOffset 這里沒用,只是用來記錄下當(dāng)前時(shí)間而已宁赤。
resumeLayer 的時(shí)候撵幽,我們需要在layer暫停的時(shí)間繼續(xù)執(zhí)行。因此礁击,我們需要用當(dāng)前時(shí)間減去暫停時(shí)間。
我們用layer的 beginTime 跳過暫停的時(shí)間繼續(xù)執(zhí)行。動(dòng)畫不變哆窿。
(事務(wù))Transactions 可以讓我們改變動(dòng)畫參數(shù)
我們對(duì)layer的所有改變都是transaction的一部分链烈。CATransation類管理動(dòng)畫的創(chuàng)建以及分組和動(dòng)畫的執(zhí)行。在大多數(shù)情況下挚躯,我們不需要?jiǎng)?chuàng)建自己的transaction强衡。每次我們想layer中添加顯示或者隱式動(dòng)畫,CoreAnimation都會(huì)自動(dòng)創(chuàng)建隱式transation码荔。不過我們也可以創(chuàng)建顯式transation來更精確的管理我們的動(dòng)畫漩勤。
我們用CATransaction類中的方法創(chuàng)建transaction,開啟一個(gè)顯式事務(wù)需要調(diào)用CATransaction類中的begin方法缩搅,結(jié)束需要調(diào)用CATransaction類的commit方法越败。在這兩個(gè)方法中間,就是我們需要改變的的內(nèi)容硼瓣。
[CATransaction begin];
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];
我們使用transations的主要原因之一是在顯式事務(wù)的范圍內(nèi)究飞,可以更改持續(xù)時(shí)間,計(jì)時(shí)功能和其他參數(shù)堂鲤。我們也可以用block捕獲動(dòng)畫完成亿傅,以便在動(dòng)畫組參數(shù)完成時(shí)通知我們。
更改動(dòng)畫參數(shù)需要使用setValue:forKey: 方法transaction修改字典中響應(yīng)的key對(duì)應(yīng)的值瘟栖。
例如:將默認(rèn)持續(xù)時(shí)間更改為10秒葵擎。具體看下面例子
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
forKey:kCATransactionAnimationDuration];
// Perform the animations
[CATransaction commit];
我們可以嵌套使用transaction。如果我們將一個(gè)transaction嵌入另一個(gè)transaction半哟,只需要重新調(diào)用begin方法酬滤。每個(gè)begin必須調(diào)用commit方法相對(duì)應(yīng)。只有在最外層transactiont提交后镜沽,CoreAnimation才會(huì)執(zhí)行關(guān)聯(lián)動(dòng)畫敏晤。其實(shí)就是一個(gè)壓棧操作了。當(dāng)椕遘裕空之后才開始進(jìn)行動(dòng)畫嘴脾。
官方代碼
[CATransaction begin]; // Outer transaction
// Change the animation duration to two seconds
[CATransaction setValue:[NSNumber numberWithFloat:2.0f]
forKey:kCATransactionAnimationDuration];
theLayer.position = CGPointMake(0.0,0.0);
[CATransaction begin]; // Inner transaction
// Change the animation duration to five seconds
[CATransaction setValue:[NSNumber numberWithFloat:5.0f]
forKey:kCATransactionAnimationDuration];
// Change the zPosition and opacity
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit]; // Inner transaction
[CATransaction commit]; // Outer transaction
這種寫法有啥用呢?
這樣我們就可以給每個(gè)屬性設(shè)定不同的動(dòng)畫執(zhí)行時(shí)間了∈叨眨看具體代碼
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
MYLayer * layer = [MYLayer layer];
layer.position= CGPointMake(150, 150);
layer.bounds = self.bounds;
layer.backgroundColor = [UIColor redColor].CGColor;
[self.layer addSublayer:layer];
self.anLayer = layer;
UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
button.backgroundColor = [UIColor blueColor];
button.frame =CGRectMake(0, 0, 100, 50);
[button addTarget:self action:@selector(button:) forControlEvents:UIControlEventTouchDown];
[self addSubview:button];
}
return self;
}
-(void)button:(UIButton *)button{
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:2.0f]
forKey:kCATransactionAnimationDuration];
self.anLayer.position =CGPointZero;
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:5]
forKey:kCATransactionAnimationDuration];
self.anLayer.opacity=0;
[CATransaction commit]; // Inner transaction
[CATransaction commit]; // Outer transaction
}
給動(dòng)畫增加透視效果
app可以在三維空間操作layer译打,單為了簡(jiǎn)單。CoreAnimation使用平行投影顯示圖層拇颅,改投影基本上將場(chǎng)景展示位二維平面奏司。這會(huì)讓具有不同深度的layer擁有相同的大小。不過我們可以通過修改layer的變換矩陣來實(shí)現(xiàn)透視效果樟插。
修改透視效果韵洋,我們需要修改sublayer的transfrom矩陣竿刁,通過將相同的透視信息應(yīng)用于sublayer,修改父類可簡(jiǎn)化我們代碼搪缨。這樣也能確保透視能正確的應(yīng)用于不同平面中彼此重疊的兄弟layer
MYLayer * layer = [MYLayer layer];
layer.position= CGPointMake(150, 150);
layer.bounds = self.bounds;
layer.backgroundColor = [UIColor redColor].CGColor;
[self.layer addSublayer:layer];
self.anLayer = layer;
self.anLayer.zPosition =-100;
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0/300;
self.layer.sublayerTransform = perspective;
UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
button.backgroundColor = [UIColor blueColor];
button.frame =CGRectMake(0, 0, 100, 50);
[button addTarget:self action:@selector(button:) forControlEvents:UIControlEventTouchDown];
[self addSubview:button];
如何提高動(dòng)畫的執(zhí)行效率
核心動(dòng)畫是提高基于應(yīng)用程序的動(dòng)畫的幀率的好方法食拜,但是它的使用并不能保證性能的提高。
小竅門
有幾種方法可以layer層實(shí)現(xiàn)更高效副编。但是负甸,與任何此類優(yōu)化一樣,在嘗試優(yōu)化之前痹届,您應(yīng)該始終測(cè)量代碼的當(dāng)前性能呻待。這為您提供了一個(gè)基線,可以用來確定優(yōu)化是否有效队腐。
盡可能使用不透明的layer
將layer的opaque屬性設(shè)置為YES可以讓核心動(dòng)畫知道它不需要為圖層維護(hù)alpha通道蚕捉。沒有alpha通道意味著合成器不需要將layer的內(nèi)容與背景內(nèi)容混合,這節(jié)省了渲染的時(shí)間香到。然而鱼冀,。如果直接將圖像分配給層的內(nèi)容屬性悠就,則無論不透明屬性中的值如何千绪,都會(huì)保留該圖像的alpha通道。該屬性對(duì)layer還是很關(guān)鍵的梗脾,對(duì)view有影響荸型,layer 也可以理解為bitimap圖的代表吧。如果你直接給layer的contents屬性直接賦值炸茧,這里需要注意的是瑞妇,image的alpha的alpha通道是還存在的,即使我們?cè)O(shè)置了layer的alpha通道梭冠,也沒有用啦辕狰。
用CAShapeLayer對(duì)象做path繪制
CAShapeLayer 類在組合時(shí)間里會(huì)將path渲染成bitmap圖來作為layer的content。其優(yōu)點(diǎn)是該layer總是以最佳可能的分辨率繪制路徑控漠,但是這種優(yōu)勢(shì)是以額外的呈現(xiàn)時(shí)間為代價(jià)的蔓倍。如果您提供的路徑復(fù)雜,則對(duì)該路徑進(jìn)行光柵化(光柵化是把頂點(diǎn)數(shù)據(jù)轉(zhuǎn)換為片元的過程盐捷,具有將圖轉(zhuǎn)化為一個(gè)個(gè)柵格組成的圖象的作用偶翅,特點(diǎn)是每個(gè)元素對(duì)應(yīng)幀緩沖區(qū)中的一像素)可能過于昂貴。如果層的大小經(jīng)常變化(因此必須頻繁地重繪)碉渡,則繪制所花費(fèi)的時(shí)間量會(huì)加在一起聚谁,并且成為性能瓶頸。
縮短繪制layer時(shí)間的一種方法是將復(fù)雜形狀分解成較簡(jiǎn)單的形狀滞诺。使用更簡(jiǎn)單的路徑并在合成器中將多個(gè)CAShapeLayer對(duì)象層疊在一起形导,可以比繪制一個(gè)大型的復(fù)雜路徑快得多环疼。這是因?yàn)槔L圖操作發(fā)生在CPU上,而合成發(fā)生在GPU上朵耕。然而秦爆,與此性質(zhì)的任何簡(jiǎn)化一樣,潛在的性能增益取決于內(nèi)容憔披。因此,在優(yōu)化之前測(cè)量代碼的性能尤其重要爸吮,這樣您就有了用于比較的基線芬膝。
相同的layer設(shè)置顯式內(nèi)容
如果我們使用相同的image給不同的layer設(shè)置圖像,我們應(yīng)該直接加載image形娇,將該image直接設(shè)置給layer的contents锰霜。直接將image賦值給layer的contents,可以防止為該layer分配后備的存儲(chǔ)內(nèi)存(減少內(nèi)存使用)桐早。這樣癣缅,該layer可以使用我們提供的layer作為后備存儲(chǔ)。當(dāng)多個(gè)layer使用相同的image哄酝,這意味著所有的這些layer共享相同的內(nèi)存友存,而不是他們自己給自己分配的同鄉(xiāng)副本。
始終將layer的size設(shè)置為整數(shù)值
為了獲取最佳結(jié)果陶衅,我們應(yīng)該將layer對(duì)象的長(zhǎng)寬設(shè)置為整數(shù)值屡立。雖然我們指定bounds用的是float類型,但是layer的邊界最終是用來創(chuàng)建bitmap圖的搀军。coreAnimation最終是使用整數(shù)值來創(chuàng)建和管理后備存儲(chǔ)和別的layer信息膨俐。(也就是說用float類型還需要進(jìn)行將float類型轉(zhuǎn)換成int類型的過程)
如果可以,用異步渲染layer
我們委托在drawLayer:inContext:method的方法和view的drawRect:方法中進(jìn)行的繪制通常情況下都是發(fā)生在主線程上罩句。然而焚刺,在某些情況下,同步繪制內(nèi)容可能無法提供最佳性能门烂。如果您注意到動(dòng)畫執(zhí)行得不好乳愉,可以嘗試啟用層上的drawsAsynchronously屬性將這些操作移動(dòng)到后臺(tái)線程。如果這樣做诅福,請(qǐng)確保繪圖代碼是線程安全的匾委。
向layer添加陰影的時(shí)候指定陰影路徑
CoreAnimation確定引用的形狀是很耗費(fèi)性能的。不要讓CoreAnimation去確認(rèn)陰影的形狀氓润,我們應(yīng)該顯示的執(zhí)行陰影的形狀赂乐。當(dāng)我們?yōu)榻o屬性執(zhí)行路徑時(shí),CoreAnimation就能使用該形狀進(jìn)行繪制并且緩存陰影效果咖气。對(duì)于形狀不改變或者很少改變的layer挨措,這樣能減少CoreAnimation的渲染而極大的提供其性能挖滤。