iOS動(dòng)畫全面解析

背景

動(dòng)畫由CoreAnimation框架作為基礎(chǔ)支持臣樱,理解動(dòng)畫之前要先理解CALayer這個(gè)東西的扮演的角色,了解它是負(fù)責(zé)呈現(xiàn)視覺內(nèi)容的東西容燕,它有3個(gè)圖層樹桥狡,還有知道CATransaction負(fù)責(zé)對(duì)layer的修改的捕獲和提交拄轻。

參考【重讀iOS】認(rèn)識(shí)CALayer

除了系統(tǒng)實(shí)現(xiàn)層面的東西颅围,還是通用意義上的動(dòng)畫。動(dòng)畫就是動(dòng)起來(lái)的畫面恨搓,畫面不斷變換產(chǎn)生變化效果院促。并不是真的有一個(gè)東西在動(dòng)筏养,一切都只是對(duì)大腦的欺騙。認(rèn)識(shí)到這個(gè)常拓,就知道動(dòng)畫需要:一系列的畫面渐溶,這些畫面之間具有相關(guān)性。

所以對(duì)于動(dòng)畫系統(tǒng)而言弄抬,它需要:(1)知道變化規(guī)律茎辐,然后根據(jù)這個(gè)規(guī)律,(2)不斷的去重繪畫面掂恕。

最簡(jiǎn)單的動(dòng)畫

有了這個(gè)認(rèn)識(shí)拖陆,再來(lái)看最簡(jiǎn)單的UIView的動(dòng)畫:

//建一個(gè)button
button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 40)];
button.backgroundColor = [UIColor orangeColor];
[self.view addSubview:button];
......
//一個(gè)簡(jiǎn)單的移動(dòng)動(dòng)畫
[UIView animateWithDuration:3 animations:^{
    button.frame = CGRectMake(0, 300, 100, 40);
}];

這是一個(gè)移動(dòng)的動(dòng)畫,移動(dòng)是因?yàn)閒rame發(fā)生了改變懊亡。然后把這個(gè)修改放在UIView animateWithDuration:的block里依啰。對(duì)于系統(tǒng)而言,它有了button開始的位置店枣,block里有了結(jié)束的位置孔飒,而且有了時(shí)間。

一個(gè)物體從一個(gè)點(diǎn)移動(dòng)到另一個(gè)點(diǎn)艰争,而且時(shí)間已知,那么就可以求出在任何一個(gè)中間時(shí)間桂对,這個(gè)物體的位置甩卓。這就是變化規(guī)律。而不斷重繪這個(gè)就是屏幕的刷新了蕉斜,這個(gè)是操作系統(tǒng)負(fù)責(zé)了逾柿,對(duì)于開發(fā)者而言,創(chuàng)造不同動(dòng)畫就在于提供不同的變化規(guī)律宅此。

CoreAnimation

UIView的一些動(dòng)畫方法只是提供了更方便的API机错,理解了CoreAnimation的動(dòng)畫,UIView的這些方法都自然清楚了父腕,直接看CoreAnimation吧弱匪。

CAAnimationHierarchy.png

這個(gè)動(dòng)畫類的繼承圖,iOS9時(shí)又添加了CASpringAnimation,繼承自CABasicAnimation璧亮。

每一個(gè)動(dòng)畫類代表了某一種類型的動(dòng)畫萧诫,代表著它們有著不同的變化規(guī)律。

CAAnimation

這個(gè)是基類枝嘶,所以它不會(huì)有特別有特色的屬性帘饶,而是一些通用性的東西。在屬性里值得注意的是timingFunctiondelegate群扶。timingFunction提供了時(shí)間的變化函數(shù)及刻,可以理解成時(shí)間流速變快或變慢镀裤。delegate就兩個(gè)方法,通知你動(dòng)畫開始了和結(jié)束了缴饭,沒什么特別的暑劝。

CAMediaTiming

這是一個(gè)協(xié)議,CAAnimation實(shí)現(xiàn)了這個(gè)協(xié)議茴扁,里面有一些跟時(shí)間相關(guān)的屬性挺有用的:

  • duration 動(dòng)畫時(shí)間
  • repeatCount 重復(fù)次數(shù)
  • autoreverses 自動(dòng)反轉(zhuǎn)動(dòng)畫铃岔,如一個(gè)動(dòng)畫是從A到B,這個(gè)為true時(shí)峭火,會(huì)接著執(zhí)行從B再到A的動(dòng)畫毁习。

CAPropertyAnimation

You do not create instances of CAPropertyAnimation: to animate the properties of a Core Animation layer, create instance of the concrete subclasses CABasicAnimation or CAKeyframeAnimation.

這個(gè)也還是一個(gè)抽象類,跟UIGestureRecognizer一樣直接構(gòu)建對(duì)象用不了的卖丸。但它的屬性還是值得解讀一下:

  • keyPath 而且有一個(gè)以keyPath為參數(shù)的構(gòu)建方法纺且,所以這個(gè)屬性是核心級(jí)別∩越回到動(dòng)畫的定義上载碌,除了需要變化規(guī)律外,還需要變化內(nèi)容衅枫。巧婦難為無(wú)米之炊嫁艇,動(dòng)畫是一種連續(xù)的變化,那就需要知道是什么在變化弦撩。這里選取內(nèi)容的方式就是指定一個(gè)屬性步咪,這個(gè)屬性是誰(shuí)的屬性?CALayer的益楼,動(dòng)畫是加載在layer上的猾漫,layer是動(dòng)畫的載體。打開CALayer的文檔感凤,在屬性的注釋里寫著Animatable的就是可以進(jìn)行動(dòng)畫的屬性悯周,也就是可以填入到這個(gè)keyPath里的東西。
    之所以是keyPath而不是key陪竿,是因?yàn)榭梢韵?code>position.y這樣使用點(diǎn)語(yǔ)法指定連續(xù)一連串的key禽翼。

從CAPropertyAnimation繼承的動(dòng)畫,也都是按照這種方式來(lái)指定變化內(nèi)容的族跛。

  • additivecumulative需要例子才好證實(shí)效果捐康,到下面再說(shuō)。
  • valueFunction 這個(gè)屬性類為CAValueFunction庸蔼,只能通過(guò)名稱來(lái)構(gòu)建解总,甚至沒有數(shù)據(jù)輸入的地方,也是從這突然看明白CAPropertyAnimation構(gòu)建對(duì)象是沒有意義的姐仅。因?yàn)闆]有數(shù)據(jù)輸入花枫,就沒有動(dòng)畫刻盐,就沒法實(shí)際應(yīng)用,這個(gè)類只是為了封裝的需要而創(chuàng)建的劳翰。

總結(jié)一下敦锌,動(dòng)畫需要3個(gè)基本要素:內(nèi)容、時(shí)間和變化規(guī)律佳簸,不同的動(dòng)畫都是在這3者上有差異乙墙。

CABasicAnimation

這個(gè)類就增加了3個(gè)屬性:fromValue toValue byValue。這3個(gè)屬性就正好是提供了輸入數(shù)據(jù)生均,確定了開始和結(jié)束狀態(tài)听想。

到現(xiàn)在,內(nèi)容(keyPath)有了马胧,時(shí)間(duration和timingFunction)有了,開始和結(jié)束狀態(tài)有了汉买。通過(guò)插值(Interpolates)就可以得到任意一個(gè)時(shí)間點(diǎn)的狀態(tài),然后渲染繪制形成一系列關(guān)聯(lián)的圖像佩脊,形成動(dòng)畫蛙粘。

非空屬性 開始值 結(jié)束值
fromValue
toValue
fromValue toValue
fromValue
byValue
fromValue fromValue+byValue
toValue
byValue
toValue -byValue toValue
fromValue fromValue currentValue
toValue currentValue toValue
byValue currentValue byValue+currentValue

上面的表表示的是當(dāng)3個(gè)屬性哪些是非空的時(shí)候,動(dòng)畫是從哪個(gè)值開始威彰、到哪個(gè)值結(jié)束出牧。而且上面的情況優(yōu)先于下面的情況。

測(cè)試additive和cumulative屬性
button.frame = CGRectMake(200, 400, 100, 40);

CABasicAnimation *basicAnim = [CABasicAnimation animationWithKeyPath:@"position"];
    
//mediaTiming
basicAnim.duration = 1;
basicAnim.repeatCount = 3;
    
//CAAnimation
basicAnim.removedOnCompletion = NO;
basicAnim.delegate = self;
    
//property
basicAnim.additive = NO;
basicAnim.cumulative = YES;
    
//basic
basicAnim.fromValue = [NSValue valueWithCGPoint:CGPointMake(100, 60)];
basicAnim.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 200)];
    
[button.layer addAnimation:basicAnim forKey:@"move"];
  • additive為true時(shí)歇盼,變化值整體加上layer的當(dāng)前值舔痕,如button開始位置為x為200,fromValue的x為100旺遮,開啟additive則動(dòng)畫開始時(shí)button的x為200+100=300,不開啟則100.
  • cumulative這個(gè)指每次的值要加上上一次循環(huán)的的結(jié)束值盈咳。這個(gè)就需要repeatCount>1的時(shí)候才能看出效果耿眉。比如這里button第一次動(dòng)畫結(jié)束后位置為(100, 200),再次開始時(shí)位置不是(100, 60)鱼响,而是加上之前的結(jié)束值鸣剪,即(200,260)。

對(duì)于不同類型的值疊加方式是不同的丈积,如矩陣筐骇,并不是直接單個(gè)元素相加,而是使用矩陣加法江滨。

CAKeyframeAnimation

終于到了明星關(guān)鍵幀動(dòng)畫铛纬。

關(guān)鍵幀動(dòng)畫,幀指一副畫面唬滑,動(dòng)畫就是一幀幀畫面連續(xù)變動(dòng)而得到的告唆。而關(guān)鍵幀棺弊,是特殊的幀,舉個(gè)例子擒悬,一個(gè)物體按照矩形的路線運(yùn)動(dòng)模她,那么提供4個(gè)角的坐標(biāo)就可以了,其他位置可以通過(guò)4個(gè)角的位置算出來(lái)懂牧。而關(guān)鍵幀就是那些不可缺少的關(guān)鍵的畫面侈净,而其他幀可以通過(guò)這些關(guān)鍵幀推算出來(lái)。

所以關(guān)鍵幀動(dòng)畫就是提供若干關(guān)鍵的數(shù)據(jù)僧凤,系統(tǒng)通過(guò)這些關(guān)鍵數(shù)據(jù)畜侦,推算出整個(gè)流程,然后完成動(dòng)畫拼弃。

有了這個(gè)理解夏伊,再看CAKeyframeAnimation的屬性里的valueskeyTimes就好理解了。

values就是各個(gè)關(guān)鍵幀的數(shù)據(jù)吻氧,keyTimes是各個(gè)關(guān)鍵幀的時(shí)間點(diǎn)溺忧,而且這兩組數(shù)據(jù)時(shí)一一對(duì)應(yīng)的,第一個(gè)value和第一個(gè)keyTime都是第一幀畫面的盯孙,以此類推鲁森。

按照這種思路,其實(shí)整個(gè)動(dòng)畫就被切割成n個(gè)小階段了振惰,每個(gè)節(jié)點(diǎn)有開始和結(jié)束數(shù)據(jù)和時(shí)間歌溉,就會(huì)發(fā)現(xiàn)這一小段其實(shí)就是一個(gè)CABasicAnimation,而CABasicAnimation也可以看成是一個(gè)特殊的關(guān)鍵幀動(dòng)畫骑晶,只有開始和結(jié)束兩個(gè)關(guān)鍵幀痛垛。

所以在使用上和CABasicAnimation并沒有特別的地方,只是從傳from桶蛔、to兩個(gè)數(shù)據(jù)匙头,變成傳一組數(shù)據(jù)罷了。

屬性path

這個(gè)是一種特殊的動(dòng)畫仔雷,如果要實(shí)現(xiàn)一個(gè)view按照某個(gè)路徑進(jìn)行移動(dòng)蹂析,就使用這個(gè)屬性,提供了路徑后碟婆,values屬性會(huì)被忽略电抚。路徑可以通過(guò)貝塞爾曲線的類提供:

//內(nèi)容
CAKeyframeAnimation *keyframeAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 
//時(shí)間
keyframeAnim.duration = 5;
//變化規(guī)律
UIBezierPath *path = [[UIBezierPath alloc] init];
[path addArcWithCenter:CGPointMake(200, 300) radius:100 startAngle:0 endAngle:M_PI*2 clockwise:YES];
keyframeAnim.path = [path CGPath];

如果不提供這個(gè)path屬性,那就要我們提供許多的點(diǎn)來(lái)完成動(dòng)畫竖共,哪怕是簡(jiǎn)單的轉(zhuǎn)圈圈蝙叛,點(diǎn)數(shù)據(jù)也超級(jí)多,越平滑的動(dòng)畫就需要越多的點(diǎn)公给。這個(gè)屬性可以說(shuō)是為了這種需求而提供的特殊福利甥温。

屬性calculationMode

這個(gè)屬性影響著關(guān)鍵幀之間的數(shù)據(jù)如何進(jìn)行推算锻煌,一個(gè)個(gè)來(lái)說(shuō):

  • kCAAnimationLinear默認(rèn)屬性,線性插值姻蚓。
  • kCAAnimationDiscrete不進(jìn)行插值宋梧,只顯示關(guān)鍵幀的畫面,看到的動(dòng)畫就是跳躍的
  • kCAAnimationPaced,這個(gè)也是線性插值狰挡,但跟第一個(gè)的區(qū)別是它是整體考慮的捂龄。舉個(gè)例子,移動(dòng)一個(gè)view,從A到B加叁,再到C倦沧,假設(shè)A-B之間距離跟B-C之間距離一樣,但是前者的時(shí)間是10s它匕,后者是20s,那么動(dòng)畫里展融,后半段就會(huì)跑得慢。而Paced類型豫柬,就忽略掉keyTimes屬性告希,達(dá)到全局勻速的效果,重新計(jì)算keyTimes烧给。這個(gè)例子里就變成A-B 15s燕偶,B-C也15s。
  • kCAAnimationCubic這個(gè)使用新的插值础嫡,算法是Catmull-Rom spline,效果就是把轉(zhuǎn)折點(diǎn)變得圓滑指么。看一下這兩種路徑對(duì)比就立馬明白,第一個(gè)是線性插值榴鼎。kCAAnimationCubicPaced這個(gè)就是兩種效果疊加伯诬。
    liner.png
cubic.png

屬性rotationMode

這個(gè)是配合路徑使用的,在使用路徑動(dòng)畫時(shí)才有意義巫财。當(dāng)值為kCAAnimationRotateAuto是盗似,會(huì)把layer旋轉(zhuǎn),使得layer自身的x軸是跟路徑相切的,并且x軸方向跟運(yùn)動(dòng)方向一致翁涤,使用kCAAnimationRotateAutoReverse也是相切桥言,但x軸方向跟運(yùn)動(dòng)方向相反萌踱。

rotateMode.png
CATransition

這個(gè)看似簡(jiǎn)單葵礼,用起來(lái)卻似乎有點(diǎn)摸不著頭腦。transition過(guò)渡的意思并鸵,這個(gè)動(dòng)畫用來(lái)完成layer的兩種狀態(tài)之間的過(guò)渡鸳粉。

問(wèn)題的核心就在這個(gè)兩種狀態(tài),查看CATransition的屬性园担,發(fā)現(xiàn)并沒有開始狀態(tài)届谈、結(jié)束狀態(tài)之類的輸入枯夜。那這兩種狀態(tài)怎么確定?How does CATransition work?這個(gè)問(wèn)題里的回答很清楚艰山,截取一段:

The way the CATransition performs this animation to to take a snapshot of the view before the layer properties are changed, and a snapshot of what the view will look like after the layer properties are changed

兩種狀態(tài)分別是:layer修改之前和之后湖雹。也就是把CATransition的動(dòng)畫加到layer上之后,這時(shí)會(huì)生成一個(gè)快照曙搬,這個(gè)開始狀態(tài)摔吏;然后你要立馬對(duì)layer進(jìn)行修改,這時(shí)layer呈現(xiàn)出另一種狀態(tài)纵装,這是修改后征讲,也就是動(dòng)畫的結(jié)束狀態(tài)。這時(shí)系統(tǒng)得到了兩張快照橡娄,在這兩張快照之間做過(guò)渡效果诗箍,就是這個(gè)動(dòng)畫。

所以如果你添加動(dòng)畫后不做修改挽唉,好像看不出什么效果滤祖。

一個(gè)例子:

[CATransaction begin];
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(150, 200, 100, 100)];
container.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1];
[self.view addSubview:container];
    
UILabel *label1 = [[UILabel alloc] initWithFrame:container.bounds];
label1.backgroundColor = [UIColor redColor];
label1.text = @"1";
label1.font = [UIFont boldSystemFontOfSize:30];
label1.textAlignment = NSTextAlignmentCenter;
[container addSubview:label1];
    
UILabel *label2 = [[UILabel alloc] initWithFrame:container.bounds];
label2.backgroundColor = [UIColor orangeColor];
label2.text = @"2";
label2.font = [UIFont boldSystemFontOfSize:30];
label2.textAlignment = NSTextAlignmentCenter;
[container addSubview:label2];
    
[CATransaction commit];
    
CATransition *fade = [[CATransition alloc] init];
fade.duration = 2;
fade.type = kCATransitionPush;
fade.subtype = kCATransitionFromRight;
    
//位置1
[container.layer addAnimation:fade forKey:nil];
//位置2
[container insertSubview:label2 belowSubview:label1];

一個(gè)view上面添加了兩個(gè)子view,動(dòng)畫加載父視圖上,添加動(dòng)畫后修改子view的上下關(guān)系來(lái)修改layer的樣式橱夭。

為什么要使用[CATransaction begin][CATransaction commit]把添加子視圖的代碼包起來(lái)呢氨距?

這本是一個(gè)bug,沒想到卻是一個(gè)對(duì)CATransaction理解加深的好例子棘劣。原因簡(jiǎn)單說(shuō):

  • 不使用顯式事務(wù)的時(shí)候俏让,對(duì)layer的修改觸發(fā)隱式事務(wù),而這種事務(wù)需要等到下一次runloop循環(huán)時(shí)才提交茬暇,
  • 所以添加動(dòng)畫的時(shí)候(位置1)事務(wù)還沒提交首昔,container的layer數(shù)據(jù)時(shí)空的,那么開始狀態(tài)就沒有糙俗,所以開始畫面是空白勒奇。
  • 等到后面隱式事務(wù)提交,這時(shí)layer的修改(位置2)已經(jīng)結(jié)束了巧骚,修改后的樣子成了動(dòng)畫結(jié)束狀態(tài)赊颠。這個(gè)是對(duì)的。
//位置1
[CATransaction begin];
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(150, 200, 100, 100)];
container.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1];
[self.view addSubview:container];
[CATransaction commit];
    
//位置5
UILabel *label1 = [[UILabel alloc] initWithFrame:container.bounds];
label1.backgroundColor = [UIColor redColor];
label1.text = @"1";
label1.font = [UIFont boldSystemFontOfSize:30];
label1.textAlignment = NSTextAlignmentCenter;
[container addSubview:label1];
    
UILabel *label2 = [[UILabel alloc] initWithFrame:container.bounds];
label2.backgroundColor = [UIColor orangeColor];
label2.text = @"2";
label2.font = [UIFont boldSystemFontOfSize:30];
label2.textAlignment = NSTextAlignmentCenter;
[container addSubview:label2];
    
//位置2
[CATransaction begin];
container.backgroundColor = [UIColor colorWithWhite:0 alpha:1];
[CATransaction commit];
    
CATransition *fade = [[CATransition alloc] init];
fade.duration = 2;
fade.type = kCATransitionPush;
fade.subtype = kCATransitionFromRight;

//位置3
[container.layer addAnimation:fade forKey:nil];
//位置4
[container insertSubview:label2 belowSubview:label1];

如果做一下簡(jiǎn)單的修改:改成位置1和位置2兩個(gè)事務(wù)劈彪,位置1時(shí)container顏色是灰色竣蹦,位置2時(shí)是黑色。中間label1和label2的處理代碼不加入顯式事務(wù)沧奴。

結(jié)果會(huì)怎么樣痘括?

動(dòng)畫變成開始畫面是灰色的container,結(jié)束狀態(tài)是label1的樣式。

fade.png

還是開始狀態(tài)的問(wèn)題,有兩個(gè)問(wèn)題:

  • 開始狀態(tài)為什么不是label2的樣式
  • 開始狀態(tài)為什么不是黑色纲菌,而是灰色

中間有一段(位置5)沒有加入顯式事務(wù)挠日,那么它就開啟了隱式事務(wù),它要等到下一次runloop循環(huán)才提交翰舌,反正是要等到這個(gè)方法執(zhí)行結(jié)束嚣潜。那么這一段都沒有加入到container的layer里,所以不會(huì)是label2的樣式椅贱。

因?yàn)殡[式事務(wù)開啟了郑原,又還沒有結(jié)束,所以位置2的事務(wù)變成了一個(gè)嵌套事務(wù)夜涕,而嵌套事務(wù)我只找到這么一句話文檔位置

Only after you commit the changes for the outermost transaction does Core Animation begin the associated animations.

很大的可能是犯犁,嵌套時(shí),內(nèi)部的事務(wù)提交的東西是提給外層的事務(wù)女器,然后一層層提交酸役,最后一層才把數(shù)據(jù)提交給CoreAnimation系統(tǒng),系統(tǒng)這時(shí)才會(huì)得到數(shù)據(jù)刷新驾胆,才會(huì)更新layer的畫面涣澡。

所以位置2的事務(wù)雖然提交了,但是它還是等到隱式事務(wù)提交才能起作用丧诺。把位置5處代碼刪掉就能看出區(qū)別入桂。

CAAnimationGroup

這個(gè)沒什么可說(shuō)的,讓多個(gè)動(dòng)畫一起執(zhí)行驳阎,顯示出符合效果抗愁。值得注意的是:

  • group的時(shí)間是有意義的,但它不影響子動(dòng)畫怎么執(zhí)行呵晚,只是到了時(shí)間就停止所有子動(dòng)畫蜘腌,不管子動(dòng)畫是否結(jié)束。所以在超出自動(dòng)化時(shí)間后饵隙,修改這個(gè)值就沒意義了撮珠。
  • 每個(gè)子動(dòng)畫是獨(dú)立執(zhí)行的,如動(dòng)畫1時(shí)長(zhǎng)1s,動(dòng)畫2時(shí)長(zhǎng)5s,那么后4s就是動(dòng)畫2的效果金矛。
CASpringAnimation

Spring是彈簧的意思芯急,這個(gè)動(dòng)畫就是像彈簧一樣擺動(dòng)的效果。

button.center = CGPointMake(0, 200);
    
CASpringAnimation *springAnim = [CASpringAnimation animationWithKeyPath:@"position"];
springAnim.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 200)];
springAnim.duration = 10;
springAnim.mass = 10;
springAnim.stiffness = 50;
springAnim.damping = 1;
springAnim.initialVelocity = 0;
springAnim.delegate = self;
    
[button.layer addAnimation:springAnim forKey:@"spring"];

這個(gè)類繼承自CABasicAnimation,所以還是需要keyPath驶俊、fromValue娶耍、toValue等數(shù)據(jù)。因?yàn)?code>keyPath存在废睦,所以它不只是用于物體的運(yùn)動(dòng)伺绽,還可以是其他的养泡,比如顏色嗜湃。CASpringAnimation提供了像彈簧一樣的變化規(guī)律奈应,而不只是運(yùn)動(dòng)的動(dòng)畫。

然后CASpringAnimation自身的屬性用于計(jì)算彈簧的運(yùn)動(dòng)模式:

  • mass 越大運(yùn)動(dòng)速度會(huì)慢购披,但衰減慢
  • stiffness 越大杖挣,速度越快,彈性越好
  • damping 越大衰減越快
  • initialVelocity 初始速度刚陡,越大動(dòng)畫開始時(shí)越快

動(dòng)畫時(shí)間不影響動(dòng)畫的運(yùn)行模式惩妇,這一點(diǎn)跟其他的動(dòng)畫不一樣,這里時(shí)間到了筐乳,物體還在動(dòng)就會(huì)直接掐掉歌殃、動(dòng)畫停止。

CALayer子類的特殊動(dòng)畫

CALayer還有一系列的子類蝙云,每種layer還有它們自己特有的動(dòng)畫氓皱。同樣,進(jìn)文檔查看屬性的注釋勃刨,帶有Animatable的是有動(dòng)畫的波材,配合CABasicAnimationCAKeyframeAnimation使用。

CATextLayer

CATextLayer有兩個(gè)動(dòng)畫屬性,fontSizeforegroundColor身隐。

CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"fontSize"];
anim.duration = 5;
anim.fromValue = @(10);
anim.toValue = @(30);
    
CATextLayer *textLayer = [[CATextLayer alloc] init];
textLayer.foregroundColor = [UIColor blackColor].CGColor;
textLayer.string = @"一串字符串";
textLayer.frame = CGRectMake(0, 300, 300, 60);
[textLayer addAnimation:anim forKey:@"text"];
    
[self.view.layer addSublayer:textLayer];
CAShapeLayer

CAShapeLayer里有許多動(dòng)畫屬性廷区,但最神奇的就是strokeStartstrokeEnd,特別是兩個(gè)組合使用的使用簡(jiǎn)直刷新認(rèn)知!<致痢隙轻!

CAShapeLayer的圖形是靠路徑提供的,而strokeStartstrokeEnd這兩個(gè)屬性就是用來(lái)設(shè)定繪制的開始和結(jié)束為止垢揩。0代表path的開始位置大脉,1代表path的結(jié)束為止,比如strokeStart設(shè)為0.5水孩,strokeEnd設(shè)為1镰矿,那么layer就只繪制path的后半段。

通過(guò)修改這兩個(gè)屬性俘种,就可以達(dá)到只繪制path一部分的目的秤标,然后它們還都支持動(dòng)畫,就可以創(chuàng)造出神奇的效果宙刘!

-(void)shaperLayerAnimations{
//圖形開始位置的動(dòng)畫
CABasicAnimation *startAnim = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
startAnim.duration = 5;
startAnim.fromValue = @(0);
startAnim.toValue = @(0.6);
    
//圖形結(jié)束位置的動(dòng)畫
CABasicAnimation *endAnim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
endAnim.duration = 5;
endAnim.fromValue = @(0.4);
endAnim.toValue = @(1);
    
//把兩個(gè)動(dòng)畫合并苍姜,繪制的區(qū)域就會(huì)不斷變動(dòng)
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = @[startAnim, endAnim];
group.duration = 5;
group.autoreverses = YES;
    
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.frame = self.view.bounds;
   
//圖形是一大一小兩個(gè)圓相切嵌套
UIBezierPath *path = [[UIBezierPath alloc] init];
[path addArcWithCenter:CGPointMake(100, 300) radius:100 startAngle:0 endAngle:M_PI*2 clockwise:YES];
[path addArcWithCenter:CGPointMake(150, 300) radius:50 startAngle:0 endAngle:M_PI*2 clockwise:YES];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor whiteColor].CGColor;
    
[shapeLayer addAnimation:group forKey:@"runningLine"];
[self.view.layer addSublayer:shapeLayer];
}

Animations

交互式動(dòng)畫

iOS10有了UIViewPropertyAnimator,可以控制動(dòng)畫的流程悬包,核心是fractionComplete這個(gè)參數(shù)衙猪,可以指定動(dòng)畫停留在某個(gè)位置。這里用一個(gè)pan手勢(shì)來(lái)調(diào)整fractionComplete,實(shí)現(xiàn)手指滑動(dòng)時(shí)垫释,動(dòng)畫跟隨執(zhí)行的效果丝格。

這感覺有點(diǎn)像,拖動(dòng)進(jìn)度條然后電影前進(jìn)或后退棵譬,隨意控制進(jìn)度显蝌。

UIViewPropertyAnimator *animator;
-(void)interactiveAnimations{
    
    button.frame = CGRectMake(200, 100, 100, 100);
    button.layer.cornerRadius = button.bounds.size.width/2;
    button.layer.masksToBounds = YES;
    
    animator = [[UIViewPropertyAnimator alloc] initWithDuration:5 curve:(UIViewAnimationCurveEaseOut) animations:^{
        button.transform = CGAffineTransformMakeScale(0.1, 0.1);
    }];
    
    [animator startAnimation];
    [animator pauseAnimation];
    
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
    [self.view addGestureRecognizer:pan];
}

float startFrac;
-(void)panAction:(UIPanGestureRecognizer *)pan{
    if (pan.state == UIGestureRecognizerStateChanged) {
        [animator pauseAnimation];
        float delta = [pan translationInView:self.view].y / self.view.bounds.size.height;
        animator.fractionComplete = startFrac+delta;
    }else if (pan.state == UIGestureRecognizerStateBegan){
        startFrac = animator.fractionComplete;
    }else if (pan.state == UIGestureRecognizerStateEnded){
        [animator startAnimation];
    }
}

ViewController的轉(zhuǎn)場(chǎng)動(dòng)畫

兩種,一個(gè)是navigation的push和pop订咸,通過(guò)navigationController的delegate提供:

  • 動(dòng)畫 UIViewControllerAnimatedTransitioning
  • 交互性動(dòng)畫 UIViewControllerInteractiveTransitioning

另一種是VC的present和dismiss,通過(guò)VC自身的transitioningDelegate提供:

  • 動(dòng)畫 UIViewControllerAnimatedTransitioning
  • 交互性動(dòng)畫 UIViewControllerInteractiveTransitioning

提供的數(shù)據(jù)時(shí)一樣的類型曼尊,所以這兩種其實(shí)邏輯上是一樣的。

先看提供動(dòng)畫的UIViewControllerAnimatedTransitioning,就兩個(gè)方法:

  • transitionDuration:讓你提供動(dòng)畫的時(shí)間
  • animateTransition: 在這里面執(zhí)行動(dòng)畫

站在設(shè)計(jì)者的角度來(lái)看一下整個(gè)流程脏嚷,這樣會(huì)幫助對(duì)這個(gè)框架的理解:

一切從push開始骆撇,nav開始push,它會(huì)去查看自己的delegate,有沒有實(shí)現(xiàn)提供轉(zhuǎn)場(chǎng)動(dòng)畫的方法,沒有就使用默認(rèn)的效果父叙,結(jié)束艾船。

有,那么就可以拿到實(shí)現(xiàn)UIViewControllerAnimatedTransitioning的對(duì)象高每,然后從這個(gè)對(duì)象里拿到動(dòng)畫時(shí)間屿岂,用這個(gè)時(shí)間去同步處理其他的操作,比如導(dǎo)航欄的動(dòng)畫鲸匿。
同時(shí)調(diào)用這個(gè)對(duì)象的animateTransition:執(zhí)行我們提供的動(dòng)畫爷怀。

這個(gè)過(guò)程了解了,就明白每個(gè)類在這個(gè)過(guò)程里的意義带欢。因?yàn)檫@些名詞都太長(zhǎng)运授,命名也很像,很容易混淆意義乔煞。

一個(gè)例子:

-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
    return _duration;
}

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    UIView *fromView = fromVC.view;
    UIView *toView = toVC.view;
    
    if (self.type == TransitionTypePush) {
        
        [transitionContext.containerView addSubview:toView];
        float scale = 0.7f;
        toView.transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(toView.bounds.size.width*(1+1/scale)/2, 0), CGAffineTransformMakeScale(scale, scale));
        
        [UIView animateWithDuration:_duration animations:^{
            
            fromView.transform = CGAffineTransformMakeScale(scale, scale);
            
            toView.transform = CGAffineTransformIdentity;
            
        } completion:^(BOOL finished) {
            fromView.transform = CGAffineTransformIdentity;
            
            [transitionContext completeTransition:YES];
        }];
    }else if (self.type == TransitionTypePop){
        
        [transitionContext.containerView insertSubview:toView belowSubview:fromView];
        float scale = 0.7f;
        toView.transform = CGAffineTransformMakeScale(scale, scale);
        
        [UIView animateWithDuration:_duration animations:^{
            
            fromView.transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(toView.bounds.size.width*(1+1/scale)/2, 0), CGAffineTransformMakeScale(scale, scale));
            
            toView.transform = CGAffineTransformIdentity;
        } completion:^(BOOL finished) {
            [fromView removeFromSuperview];
            
            [transitionContext completeTransition:YES];
        }];
    }
}

push時(shí)的效果是進(jìn)來(lái)的view吁朦,即toView從右邊緣一邊進(jìn)來(lái)一邊放大,直到鋪滿屏幕渡贾;退出的view,即fromView,逐漸縮小逗宜。合在一起有一種滾筒的感覺。pop時(shí)就是反操作空骚。

除了動(dòng)畫內(nèi)容之外纺讲,值得注意的是:

  • 第一個(gè)方法提供的時(shí)間用來(lái)做轉(zhuǎn)場(chǎng)時(shí)的其他變化,如push時(shí)系統(tǒng)導(dǎo)航欄的動(dòng)畫囤屹,而且在這期間交互式禁止的熬甚。所以這個(gè)時(shí)間跟下面我們提供的動(dòng)畫時(shí)間要一樣。
  • toView需要我們自己加到containerView
  • 不管動(dòng)畫是否執(zhí)行成功肋坚,一定要調(diào)用[transitionContext completeTransition:],這個(gè)標(biāo)識(shí)這一次的VC切換結(jié)束了乡括,否則后面的push肃廓、pop等都沒效果。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诲泌,一起剝皮案震驚了整個(gè)濱河市盲赊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌档礁,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吝沫,死亡現(xiàn)場(chǎng)離奇詭異呻澜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)惨险,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門羹幸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人辫愉,你說(shuō)我怎么就攤上這事栅受。” “怎么了恭朗?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵屏镊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我痰腮,道長(zhǎng)而芥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任膀值,我火速辦了婚禮棍丐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沧踏。我一直安慰自己歌逢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布翘狱。 她就那樣靜靜地躺著秘案,像睡著了一般。 火紅的嫁衣襯著肌膚如雪潦匈。 梳的紋絲不亂的頭發(fā)上踏烙,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音历等,去河邊找鬼讨惩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛寒屯,可吹牛的內(nèi)容都是我干的荐捻。 我是一名探鬼主播黍少,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼处面!你這毒婦竟也來(lái)了厂置?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤魂角,失蹤者是張志新(化名)和其女友劉穎昵济,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體野揪,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡访忿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年橱乱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笔喉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惭聂。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雷则,死狀恐怖契耿,靈堂內(nèi)的尸體忽然破棺而出廊佩,到底是詐尸還是另有隱情链烈,我是刑警寧澤援奢,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布憎茂,位于F島的核電站珍语,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏竖幔。R本人自食惡果不足惜廊酣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赏枚。 院中可真熱鬧亡驰,春花似錦、人聲如沸饿幅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)栗恩。三九已至透乾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間磕秤,已是汗流浹背乳乌。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留市咆,地道東北人汉操。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蒙兰,于是被迫代替她去往敵國(guó)和親磷瘤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芒篷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過(guò)程并不復(fù)雜采缚,今天將帶大家一窺ios動(dòng)畫全貌针炉。在這里你可以看...
    每天刷兩次牙閱讀 8,465評(píng)論 6 30
  • 1 CALayer IOS SDK詳解之CALayer(一) http://doc.okbase.net/Hell...
    Kevin_Junbaozi閱讀 5,128評(píng)論 3 23
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過(guò)程并不復(fù)雜扳抽,今天將帶大家一窺iOS動(dòng)畫全貌篡帕。在這里你可以看...
    F麥子閱讀 5,094評(píng)論 5 13
  • 如果想讓事情變得順利,只有靠自己--夏爾·紀(jì)堯姆 上一章介紹了隱式動(dòng)畫的概念贸呢。隱式動(dòng)畫是在iOS平臺(tái)創(chuàng)建動(dòng)態(tài)用戶界...
    夜空下最亮的亮點(diǎn)閱讀 1,925評(píng)論 0 1
  • 在iOS實(shí)際開發(fā)中常用的動(dòng)畫無(wú)非是以下四種:UIView動(dòng)畫镰烧,核心動(dòng)畫,幀動(dòng)畫贮尉,自定義轉(zhuǎn)場(chǎng)動(dòng)畫拌滋。 1.UIView...
    請(qǐng)叫我周小帥閱讀 3,078評(píng)論 1 23