背景
動畫由CoreAnimation框架作為基礎(chǔ)支持,理解動畫之前要先理解CALayer這個東西的扮演的角色,了解它是負責(zé)呈現(xiàn)視覺內(nèi)容的東西,它有3個圖層樹,還有知道CATransaction負責(zé)對layer的修改的捕獲和提交。
除了系統(tǒng)實現(xiàn)層面的東西,還是通用意義上的動畫。動畫就是動起來的畫面,畫面不斷變換產(chǎn)生變化效果。并不是真的有一個東西在動,一切都只是對大腦的欺騙。認識到這個,就知道動畫需要:一系列的畫面,這些畫面之間具有相關(guān)性。
所以對于動畫系統(tǒng)而言,它需要:(1)知道變化規(guī)律,然后根據(jù)這個規(guī)律,(2)不斷的去重繪畫面。
最簡單的動畫
有了這個認識,再來看最簡單的UIView的動畫:
//建一個button
button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 40)];
button.backgroundColor = [UIColor orangeColor];
[self.view addSubview:button];
......
//一個簡單的移動動畫
[UIView animateWithDuration:3 animations:^{
button.frame = CGRectMake(0, 300, 100, 40);
}];
這是一個移動的動畫,移動是因為frame發(fā)生了改變。然后把這個修改放在UIView animateWithDuration:的block里。對于系統(tǒng)而言,它有了button開始的位置,block里有了結(jié)束的位置,而且有了時間。
一個物體從一個點移動到另一個點,而且時間已知,那么就可以求出在任何一個中間時間,這個物體的位置。這就是變化規(guī)律。而不斷重繪這個就是屏幕的刷新了,這個是操作系統(tǒng)負責(zé)了,對于開發(fā)者而言,創(chuàng)造不同動畫就在于提供不同的變化規(guī)律。
CoreAnimation
UIView的一些動畫方法只是提供了更方便的API,理解了CoreAnimation的動畫,UIView的這些方法都自然清楚了,直接看CoreAnimation吧。

這個動畫類的繼承圖,iOS9時又添加了CASpringAnimation,繼承自CABasicAnimation。
每一個動畫類代表了某一種類型的動畫,代表著它們有著不同的變化規(guī)律。
CAAnimation
這個是基類,所以它不會有特別有特色的屬性,而是一些通用性的東西。在屬性里值得注意的是timingFunction和delegate。timingFunction提供了時間的變化函數(shù),可以理解成時間流速變快或變慢。delegate就兩個方法,通知你動畫開始了和結(jié)束了,沒什么特別的。
CAMediaTiming
這是一個協(xié)議,CAAnimation實現(xiàn)了這個協(xié)議,里面有一些跟時間相關(guān)的屬性挺有用的:
- duration 動畫時間
- repeatCount 重復(fù)次數(shù)
- autoreverses 自動反轉(zhuǎn)動畫,如一個動畫是從A到B,這個為true時,會接著執(zhí)行從B再到A的動畫。
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.
這個也還是一個抽象類,跟UIGestureRecognizer一樣直接構(gòu)建對象用不了的。但它的屬性還是值得解讀一下:
-
keyPath而且有一個以keyPath為參數(shù)的構(gòu)建方法,所以這個屬性是核心級別。回到動畫的定義上,除了需要變化規(guī)律外,還需要變化內(nèi)容。巧婦難為無米之炊,動畫是一種連續(xù)的變化,那就需要知道是什么在變化。這里選取內(nèi)容的方式就是指定一個屬性,這個屬性是誰的屬性?CALayer的,動畫是加載在layer上的,layer是動畫的載體。打開CALayer的文檔,在屬性的注釋里寫著Animatable的就是可以進行動畫的屬性,也就是可以填入到這個keyPath里的東西。
之所以是keyPath而不是key,是因為可以像position.y這樣使用點語法指定連續(xù)一連串的key。
從CAPropertyAnimation繼承的動畫,也都是按照這種方式來指定變化內(nèi)容的。
-
additive和cumulative需要例子才好證實效果,到下面再說。 -
valueFunction這個屬性類為CAValueFunction,只能通過名稱來構(gòu)建,甚至沒有數(shù)據(jù)輸入的地方,也是從這突然看明白CAPropertyAnimation構(gòu)建對象是沒有意義的。因為沒有數(shù)據(jù)輸入,就沒有動畫,就沒法實際應(yīng)用,這個類只是為了封裝的需要而創(chuàng)建的。
總結(jié)一下,動畫需要3個基本要素:內(nèi)容、時間和變化規(guī)律,不同的動畫都是在這3者上有差異。
CABasicAnimation
這個類就增加了3個屬性:fromValue toValue byValue。這3個屬性就正好是提供了輸入數(shù)據(jù),確定了開始和結(jié)束狀態(tài)。
到現(xiàn)在,內(nèi)容(keyPath)有了,時間(duration和timingFunction)有了,開始和結(jié)束狀態(tài)有了。通過插值(Interpolates)就可以得到任意一個時間點的狀態(tài),然后渲染繪制形成一系列關(guān)聯(lián)的圖像,形成動畫。
| 非空屬性 | 開始值 | 結(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個屬性哪些是非空的時候,動畫是從哪個值開始、到哪個值結(jié)束。而且上面的情況優(yōu)先于下面的情況。
測試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時,變化值整體加上layer的當(dāng)前值,如button開始位置為x為200,fromValue的x為100,開啟additive則動畫開始時button的x為200+100=300,不開啟則100. -
cumulative這個指每次的值要加上上一次循環(huán)的的結(jié)束值。這個就需要repeatCount>1的時候才能看出效果。比如這里button第一次動畫結(jié)束后位置為(100, 200),再次開始時位置不是(100, 60),而是加上之前的結(jié)束值,即(200,260)。
對于不同類型的值疊加方式是不同的,如矩陣,并不是直接單個元素相加,而是使用矩陣加法。
CAKeyframeAnimation
終于到了明星關(guān)鍵幀動畫。
關(guān)鍵幀動畫,幀指一副畫面,動畫就是一幀幀畫面連續(xù)變動而得到的。而關(guān)鍵幀,是特殊的幀,舉個例子,一個物體按照矩形的路線運動,那么提供4個角的坐標就可以了,其他位置可以通過4個角的位置算出來。而關(guān)鍵幀就是那些不可缺少的關(guān)鍵的畫面,而其他幀可以通過這些關(guān)鍵幀推算出來。
所以關(guān)鍵幀動畫就是提供若干關(guān)鍵的數(shù)據(jù),系統(tǒng)通過這些關(guān)鍵數(shù)據(jù),推算出整個流程,然后完成動畫。
有了這個理解,再看CAKeyframeAnimation的屬性里的values和keyTimes就好理解了。
values就是各個關(guān)鍵幀的數(shù)據(jù),keyTimes是各個關(guān)鍵幀的時間點,而且這兩組數(shù)據(jù)時一一對應(yīng)的,第一個value和第一個keyTime都是第一幀畫面的,以此類推。
按照這種思路,其實整個動畫就被切割成n個小階段了,每個節(jié)點有開始和結(jié)束數(shù)據(jù)和時間,就會發(fā)現(xiàn)這一小段其實就是一個CABasicAnimation,而CABasicAnimation也可以看成是一個特殊的關(guān)鍵幀動畫,只有開始和結(jié)束兩個關(guān)鍵幀。
所以在使用上和CABasicAnimation并沒有特別的地方,只是從傳from、to兩個數(shù)據(jù),變成傳一組數(shù)據(jù)罷了。
屬性path
這個是一種特殊的動畫,如果要實現(xiàn)一個view按照某個路徑進行移動,就使用這個屬性,提供了路徑后,values屬性會被忽略。路徑可以通過貝塞爾曲線的類提供:
//內(nèi)容
CAKeyframeAnimation *keyframeAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
//時間
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];
如果不提供這個path屬性,那就要我們提供許多的點來完成動畫,哪怕是簡單的轉(zhuǎn)圈圈,點數(shù)據(jù)也超級多,越平滑的動畫就需要越多的點。這個屬性可以說是為了這種需求而提供的特殊福利。
屬性calculationMode
這個屬性影響著關(guān)鍵幀之間的數(shù)據(jù)如何進行推算,一個個來說:
-
kCAAnimationLinear默認屬性,線性插值。 -
kCAAnimationDiscrete不進行插值,只顯示關(guān)鍵幀的畫面,看到的動畫就是跳躍的 -
kCAAnimationPaced,這個也是線性插值,但跟第一個的區(qū)別是它是整體考慮的。舉個例子,移動一個view,從A到B,再到C,假設(shè)A-B之間距離跟B-C之間距離一樣,但是前者的時間是10s,后者是20s,那么動畫里,后半段就會跑得慢。而Paced類型,就忽略掉keyTimes屬性,達到全局勻速的效果,重新計算keyTimes。這個例子里就變成A-B 15s,B-C也15s。 -
kCAAnimationCubic這個使用新的插值,算法是Catmull-Rom spline,效果就是把轉(zhuǎn)折點變得圓滑??匆幌逻@兩種路徑對比就立馬明白,第一個是線性插值。kCAAnimationCubicPaced這個就是兩種效果疊加。
liner.png

屬性rotationMode
這個是配合路徑使用的,在使用路徑動畫時才有意義。當(dāng)值為kCAAnimationRotateAuto是,會把layer旋轉(zhuǎn),使得layer自身的x軸是跟路徑相切的,并且x軸方向跟運動方向一致,使用kCAAnimationRotateAutoReverse也是相切,但x軸方向跟運動方向相反。

CATransition
這個看似簡單,用起來卻似乎有點摸不著頭腦。transition過渡的意思,這個動畫用來完成layer的兩種狀態(tài)之間的過渡。
問題的核心就在這個兩種狀態(tài),查看CATransition的屬性,發(fā)現(xiàn)并沒有開始狀態(tài)、結(jié)束狀態(tài)之類的輸入。那這兩種狀態(tài)怎么確定?How does CATransition work?這個問題里的回答很清楚,截取一段:
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的動畫加到layer上之后,這時會生成一個快照,這個開始狀態(tài);然后你要立馬對layer進行修改,這時layer呈現(xiàn)出另一種狀態(tài),這是修改后,也就是動畫的結(jié)束狀態(tài)。這時系統(tǒng)得到了兩張快照,在這兩張快照之間做過渡效果,就是這個動畫。
所以如果你添加動畫后不做修改,好像看不出什么效果。
一個例子:
[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];
一個view上面添加了兩個子view,動畫加載父視圖上,添加動畫后修改子view的上下關(guān)系來修改layer的樣式。
為什么要使用[CATransaction begin]和[CATransaction commit]把添加子視圖的代碼包起來呢?
這本是一個bug,沒想到卻是一個對CATransaction理解加深的好例子。原因簡單說:
- 不使用顯式事務(wù)的時候,對layer的修改觸發(fā)隱式事務(wù),而這種事務(wù)需要等到下一次runloop循環(huán)時才提交,
- 所以添加動畫的時候(位置1)事務(wù)還沒提交,
container的layer數(shù)據(jù)時空的,那么開始狀態(tài)就沒有,所以開始畫面是空白。 - 等到后面隱式事務(wù)提交,這時layer的修改(位置2)已經(jīng)結(jié)束了,修改后的樣子成了動畫結(jié)束狀態(tài)。這個是對的。
//位置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];
如果做一下簡單的修改:改成位置1和位置2兩個事務(wù),位置1時container顏色是灰色,位置2時是黑色。中間label1和label2的處理代碼不加入顯式事務(wù)。
結(jié)果會怎么樣?
動畫變成開始畫面是灰色的container,結(jié)束狀態(tài)是label1的樣式。

還是開始狀態(tài)的問題,有兩個問題:
- 開始狀態(tài)為什么不是label2的樣式
- 開始狀態(tài)為什么不是黑色,而是灰色
中間有一段(位置5)沒有加入顯式事務(wù),那么它就開啟了隱式事務(wù),它要等到下一次runloop循環(huán)才提交,反正是要等到這個方法執(zhí)行結(jié)束。那么這一段都沒有加入到container的layer里,所以不會是label2的樣式。
因為隱式事務(wù)開啟了,又還沒有結(jié)束,所以位置2的事務(wù)變成了一個嵌套事務(wù),而嵌套事務(wù)我只找到這么一句話文檔位置:
Only after you commit the changes for the outermost transaction does Core Animation begin the associated animations.
很大的可能是,嵌套時,內(nèi)部的事務(wù)提交的東西是提給外層的事務(wù),然后一層層提交,最后一層才把數(shù)據(jù)提交給CoreAnimation系統(tǒng),系統(tǒng)這時才會得到數(shù)據(jù)刷新,才會更新layer的畫面。
所以位置2的事務(wù)雖然提交了,但是它還是等到隱式事務(wù)提交才能起作用。把位置5處代碼刪掉就能看出區(qū)別。
CAAnimationGroup
這個沒什么可說的,讓多個動畫一起執(zhí)行,顯示出符合效果。值得注意的是:
- group的時間是有意義的,但它不影響子動畫怎么執(zhí)行,只是到了時間就停止所有子動畫,不管子動畫是否結(jié)束。所以在超出自動化時間后,修改這個值就沒意義了。
- 每個子動畫是獨立執(zhí)行的,如動畫1時長1s,動畫2時長5s,那么后4s就是動畫2的效果。
CASpringAnimation
Spring是彈簧的意思,這個動畫就是像彈簧一樣擺動的效果。
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"];
這個類繼承自CABasicAnimation,所以還是需要keyPath、fromValue、toValue等數(shù)據(jù)。因為keyPath存在,所以它不只是用于物體的運動,還可以是其他的,比如顏色。CASpringAnimation提供了像彈簧一樣的變化規(guī)律,而不只是運動的動畫。
然后CASpringAnimation自身的屬性用于計算彈簧的運動模式:
- mass 越大運動速度會慢,但衰減慢
- stiffness 越大,速度越快,彈性越好
- damping 越大衰減越快
- initialVelocity 初始速度,越大動畫開始時越快
動畫時間不影響動畫的運行模式,這一點跟其他的動畫不一樣,這里時間到了,物體還在動就會直接掐掉、動畫停止。
CALayer子類的特殊動畫
CALayer還有一系列的子類,每種layer還有它們自己特有的動畫。同樣,進文檔查看屬性的注釋,帶有Animatable的是有動畫的,配合CABasicAnimation和CAKeyframeAnimation使用。
CATextLayer
CATextLayer有兩個動畫屬性,fontSize和foregroundColor。
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里有許多動畫屬性,但最神奇的就是strokeStart和strokeEnd,特別是兩個組合使用的使用簡直刷新認知?。。?/p>
CAShapeLayer的圖形是靠路徑提供的,而strokeStart和strokeEnd這兩個屬性就是用來設(shè)定繪制的開始和結(jié)束為止。0代表path的開始位置,1代表path的結(jié)束為止,比如strokeStart設(shè)為0.5,strokeEnd設(shè)為1,那么layer就只繪制path的后半段。
通過修改這兩個屬性,就可以達到只繪制path一部分的目的,然后它們還都支持動畫,就可以創(chuàng)造出神奇的效果!
-(void)shaperLayerAnimations{
//圖形開始位置的動畫
CABasicAnimation *startAnim = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
startAnim.duration = 5;
startAnim.fromValue = @(0);
startAnim.toValue = @(0.6);
//圖形結(jié)束位置的動畫
CABasicAnimation *endAnim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
endAnim.duration = 5;
endAnim.fromValue = @(0.4);
endAnim.toValue = @(1);
//把兩個動畫合并,繪制的區(qū)域就會不斷變動
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;
//圖形是一大一小兩個圓相切嵌套
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];
}
交互式動畫
iOS10有了UIViewPropertyAnimator,可以控制動畫的流程,核心是fractionComplete這個參數(shù),可以指定動畫停留在某個位置。這里用一個pan手勢來調(diào)整fractionComplete,實現(xiàn)手指滑動時,動畫跟隨執(zhí)行的效果。
這感覺有點像,拖動進度條然后電影前進或后退,隨意控制進度。
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)場動畫
兩種,一個是navigation的push和pop,通過navigationController的delegate提供:
- 動畫
UIViewControllerAnimatedTransitioning - 交互性動畫
UIViewControllerInteractiveTransitioning
另一種是VC的present和dismiss,通過VC自身的transitioningDelegate提供:
- 動畫
UIViewControllerAnimatedTransitioning - 交互性動畫
UIViewControllerInteractiveTransitioning
提供的數(shù)據(jù)時一樣的類型,所以這兩種其實邏輯上是一樣的。
先看提供動畫的UIViewControllerAnimatedTransitioning,就兩個方法:
-
transitionDuration:讓你提供動畫的時間 -
animateTransition:在這里面執(zhí)行動畫
站在設(shè)計者的角度來看一下整個流程,這樣會幫助對這個框架的理解:
一切從push開始,nav開始push,它會去查看自己的delegate,有沒有實現(xiàn)提供轉(zhuǎn)場動畫的方法,沒有就使用默認的效果,結(jié)束。
有,那么就可以拿到實現(xiàn)UIViewControllerAnimatedTransitioning的對象,然后從這個對象里拿到動畫時間,用這個時間去同步處理其他的操作,比如導(dǎo)航欄的動畫。
同時調(diào)用這個對象的animateTransition:執(zhí)行我們提供的動畫。
這個過程了解了,就明白每個類在這個過程里的意義。因為這些名詞都太長,命名也很像,很容易混淆意義。
一個例子:
-(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時的效果是進來的view,即toView從右邊緣一邊進來一邊放大,直到鋪滿屏幕;退出的view,即fromView,逐漸縮小。合在一起有一種滾筒的感覺。pop時就是反操作。
除了動畫內(nèi)容之外,值得注意的是:
- 第一個方法提供的時間用來做轉(zhuǎn)場時的其他變化,如push時系統(tǒng)導(dǎo)航欄的動畫,而且在這期間交互式禁止的。所以這個時間跟下面我們提供的動畫時間要一樣。
-
toView需要我們自己加到containerView上 - 不管動畫是否執(zhí)行成功,一定要調(diào)用
[transitionContext completeTransition:],這個標識這一次的VC切換結(jié)束了,否則后面的push、pop等都沒效果。
