上一章介紹了隱式動畫的概念骑祟。隱式動畫是在iOS
平臺創(chuàng)建動態(tài)用戶界面的一種直接方式涣澡,也是UIKit
動畫機制的基礎(chǔ)狐肢,不過它并不能涵蓋所有的動畫類型惠爽。在這一章 中癌蓖,我們將要研究一下顯式動畫,它能夠?qū)σ恍傩宰鲋付ǖ淖远x動畫婚肆,或者創(chuàng)建非線性動畫费坊,比如沿著任意一條曲線移動。
屬性動畫
CAAnimationDelegate
在任何頭文件中都找不到旬痹,但是可以在CAAnimation
頭文件或者蘋果開發(fā)者文檔中找到相關(guān)函數(shù)附井。在這個例子中,我們用- animationDidStop: finished:
方法在動畫結(jié)束之后來更新圖層backgroundColor
的两残。
當(dāng)更新屬性的時候永毅,我們需要設(shè)置一個新的事務(wù),并且禁用圖層行為人弓。否則動畫會發(fā)生兩次沼死,一個是因為顯式的 CABasicAnimation
,另一次是因為隱式動畫崔赌,具體實現(xiàn)代碼如下意蛀。
- (void)viewDidLoad {
[super viewDidLoad];
CALayer *colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(0, 0, 100, 100);
colorLayer.position = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 0.5);
colorLayer.backgroundColor = [UIColor redColor].CGColor;
self.colorLayer = colorLayer;
[self.view.layer addSublayer:colorLayer];
}
- (IBAction)changeColor:(id)sender {
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
//create a basic animation
CABasicAnimation *baseAnimation = [CABasicAnimation animation];
baseAnimation.keyPath = @"backgroundColor";
baseAnimation.toValue = (__bridge id)color.CGColor;
baseAnimation.delegate = self;
[self.colorLayer addAnimation:baseAnimation forKey:nil];
}
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{
[CATransaction begin];
[CATransaction setDisableActions:true];
[CATransaction setDisableActions:0.25];
self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
[CATransaction commit];
}
對 CAAnimation
而言,使用委托模式而不是一個完成塊會帶來一個問題健芭,就是當(dāng) 你有多個動畫的時候县钥,無法在在回調(diào)方法中區(qū)分。在一個視圖控制器中創(chuàng)建動畫的 時候慈迈,通常會用控制器本身作為一個委托(如上面所示)若贮,但是所有的動畫都會調(diào)用同一個回調(diào)方法,所以你就需要判斷到底是那個圖層的調(diào)用。
動畫本身會作為一個參數(shù)傳入委托的方法谴麦,也許你會認(rèn)為可以控制器中把動畫存儲
為一個屬性蠢沿,然后在回調(diào)用比較,但實際上并不起作用匾效,因為委托傳入的動畫參數(shù)
是原始值的一個深拷貝舷蟀,從而不是同一個值。
當(dāng)使用-animation:forKey:
把動畫添加到圖層, 這里有一個到目前為止我們都設(shè)置為nil
的key
參數(shù)面哼。這里的鍵是-animationForKey:
方法找到對應(yīng)動 畫的唯一標(biāo)識符野宜,而當(dāng)前動畫的所有鍵都可以用animationKeys
獲取。如果我們 對每個動畫都關(guān)聯(lián)一個唯一的鍵精绎,就可以對每個圖層循環(huán)所有鍵速缨,然后調(diào)用 - animationForKey:
來比對結(jié)果锌妻。盡管這不是一個優(yōu)雅的實現(xiàn)代乃。
幸運的是,還有一種更加簡單的方法仿粹。像所有的 NSObject
子類一 樣搁吓,CAAnimation
實現(xiàn)了KVC
(鍵-值-編碼)協(xié)議,于是你可以用 - setValue:forKey:
和- valueForKey:
方法來存取屬性吭历。但是CAAnimation
有 一個不同的性能:它更像一個NSDictionary
堕仔,可以讓你隨意設(shè)置鍵值對,即使和你使用的動畫類所聲明的屬性并不匹配晌区。
這意味著你可以對動畫用任意類型打標(biāo)簽摩骨。在這里,我們給UIView
類型的指針添 加的動畫朗若,所以可以簡單地判斷動畫到底屬于哪個視圖恼五,然后在委托方法中用這個 信息正確地更新鐘的指針。
在模擬器上運行的很好哭懈,但當(dāng)真 正跑在iOS設(shè)備上時灾馒,我們發(fā)現(xiàn)在 -animationDidStop:finished: 委托方法調(diào)用 之前,指針會迅速返回到原始值遣总。
問題在于回調(diào)方法在動畫完成之前已經(jīng)被調(diào)用了睬罗,但不能保證這發(fā)生在屬性動畫返
回初始狀態(tài)之前。這同時也很好地說明了為什么要在真實的設(shè)備上測試動畫代碼旭斥,
而不僅僅是模擬器容达。
我們可以用一個 fillMode
屬性來解決這個問題,下一章會詳細(xì)說明垂券,這里知道在 動畫之前設(shè)置它比在動畫結(jié)束之后更新屬性更加方便董饰。
關(guān)鍵幀動畫
CABasicAnimation
揭示了大多數(shù)隱式動畫背后依賴的機制,這的確很有趣,但是顯示地給圖層添加CABasicAnimation
相較于隱式動畫而言卒暂,只能說費力不討好啄栓。
CAKeyframeAnimation
是另一種UIKit
沒有暴露出來但功能強大的類。和CABasicAnimation
類似也祠, CAKeyframeAnimation
同樣是CAPropertyAnimation
的一個子類昙楚,它依然作用于單一的一個屬性,但是和CABasicAnimation
不一樣的是诈嘿,它不限制于設(shè)置一個起始和結(jié)束的值堪旧,而是可以根據(jù)一連串隨意的值來做動畫。
關(guān)鍵幀起源于傳動動畫奖亚,意思是指主導(dǎo)的動畫在顯著改變發(fā)生時重繪當(dāng)前幀(也就 是關(guān)鍵幀)淳梦,每幀之間剩下的繪制(可以通過關(guān)鍵幀推算出)將由熟練的藝術(shù)家來 完成。 CAKeyframeAnimation
也是同樣的道理:你提供了顯著的幀昔字,然后Core Animation
在每幀之間進(jìn)行插入爆袍。
我們可以用之前使用顏色圖層的例子來演示,設(shè)置一個顏色的數(shù)組作郭,然后通過關(guān)鍵 幀動畫播放出來
- (void)viewDidLoad {
[super viewDidLoad];
CALayer *colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(0, 0, 100, 100);
colorLayer.backgroundColor = [UIColor redColor].CGColor;
colorLayer.position = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 0.5);
self.colorLayer = colorLayer;
[self.view.layer addSublayer:colorLayer];
}
- (IBAction)changeColor:(id)sender {
CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];
keyAnimation.keyPath = @"backgroundColor";
keyAnimation.duration = 2.0;
keyAnimation.values = @[(__bridge id)[UIColor blueColor].CGColor,
(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor
];
[self.colorLayer addAnimation:keyAnimation forKey:nil];
}
注意到序列中開始和結(jié)束的顏色都是藍(lán)色陨囊,這是因為CAKeyframeAnimation
并不 能自動把當(dāng)前值作為第一幀(就像CABasicAnimation
那樣把fromValue
設(shè)為 nil )。動畫會在開始的時候突然跳轉(zhuǎn)到第一幀的值夹攒,然后在動畫結(jié)束的時候 突然恢復(fù)到原始的值蜘醋。所以為了動畫的平滑特性,我們需要開始和結(jié)束的關(guān)鍵幀來 匹配當(dāng)前屬性的值咏尝。
當(dāng)然可以創(chuàng)建一個結(jié)束和開始值不同的動畫压语,那樣的話就需要在動畫啟動之前手動更新屬性和最后一幀的值保持一致,就和之前討論的一樣编检。
我們用duration
屬性把動畫時間從默認(rèn)的0.25
秒增加到2
秒胎食,以便于動畫做的不 那么快。運行它蒙谓,你會發(fā)現(xiàn)動畫通過顏色不斷循環(huán)斥季,但效果看起來有些奇怪。原因 在于動畫以一個恒定的步調(diào)在運行累驮。當(dāng)在每個動畫之間過渡的時候并沒有減速酣倾,這 就產(chǎn)生了一個略微奇怪的效果,為了讓動畫看起來更自然谤专,我們需要調(diào)整一下緩 沖躁锡,第十章將會詳細(xì)說明。
提供一個數(shù)組的值就可以按照顏色變化做動畫置侍,但一般來說用數(shù)組來描述動畫運動并不直觀映之。
CAKeyframeAnimation
有另一種方式去指定動畫拦焚,就是使用CGPath
。path
屬性可以用一種直觀的方式杠输,使用Core Graphics
函數(shù)定義運動序列來繪制動畫赎败。
我們來用一個宇宙飛船沿著一個簡單曲線的實例演示一下。為了創(chuàng)建路徑蠢甲,我們需要使用一個三次貝塞爾曲線僵刮,它是一種使用開始點,結(jié)束點和另外兩個控制點來定義形狀的曲線鹦牛,可以通過使用一個基于C
的Core Graphics
繪圖指令來創(chuàng)建搞糕,不過用UIKit
提供的 UIBezierPath
類會更簡單。
我們這次用CAShapeLayer
來在屏幕上繪制曲線曼追,盡管對動畫來說并不是必須 的窍仰,但這會讓我們的動畫更加形象。繪制完 CGPath
之后礼殊,我們用它來創(chuàng)建一 個CAKeyframeAnimation
差导,然后用它來應(yīng)用到我們的宇宙飛船蹂空。
運行示例刚夺,你會發(fā)現(xiàn)飛船的動畫有些不太真實屹蚊,這是因為當(dāng)它運動的時候永遠(yuǎn)指向 右邊悟民,而不是指向曲線切線的方向坝辫。你可以調(diào)整它的 affineTransform
來對運動 方向做動畫,但很可能和其它的動畫沖突射亏。
蘋果預(yù)見到了這點近忙,并且給CAKeyFrameAnimation
添加了一個rotationMode
的屬性。設(shè)置它為常量KCAAnimationRotateAuto
智润,圖層將會根據(jù)曲線的切線自動旋轉(zhuǎn)及舍。
- (void)viewDidLoad {
[super viewDidLoad];
UIBezierPath *bezierPath = [[UIBezierPath alloc]init];
[bezierPath moveToPoint:CGPointMake(0, 150)];
[bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(200, 200) controlPoint2:CGPointMake(150, 50)];
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = bezierPath.CGPath;
pathLayer.fillColor = [UIColor clearColor].CGColor;
pathLayer.strokeColor = [UIColor redColor].CGColor;
pathLayer.lineWidth = 3.0f;
[self.containerView.layer addSublayer:pathLayer];
CALayer *shipLayer = [CALayer layer];
shipLayer.frame = CGRectMake(0, 0, 64, 64);
shipLayer.position = CGPointMake(0, 150);
shipLayer.contents = (__bridge id)[UIImage imageNamed:@"ship.png"].CGImage;
[self.containerView.layer addSublayer:shipLayer];
CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];
keyAnimation.keyPath = @"position";
keyAnimation.duration = 4.0;
keyAnimation.path = bezierPath.CGPath;
keyAnimation.rotationMode = kCAAnimationRotateAuto;
[shipLayer addAnimation:keyAnimation forKey:nil];
}
虛擬屬性
之前提到過屬性動畫實際上是針對于關(guān)鍵路徑而不是一個鍵,這就意味著可以對子屬性甚至是虛擬屬性做動畫窟绷。但是虛擬屬性到底是什么呢?
考慮一個旋轉(zhuǎn)的動畫:如果想要對一個物體做旋轉(zhuǎn)的動畫锯玛,那就需要作用
于transform
屬性,因為 CALayer
沒有顯式提供角度或者方向之類的屬性兼蜈,代 碼如下所示
用 transform.rotation
而不是 transform
做動畫的好處 如下:
- 我們可以不通過關(guān)鍵幀一步旋轉(zhuǎn)多于180度的動畫攘残。
- 可以用相對值而不是絕對值旋轉(zhuǎn)(設(shè)置
byValue
而不是toValue
)。 - 可以不用創(chuàng)建
CATransform3D
为狸,而是使用一個簡單的數(shù)值來指定角度歼郭。 - 不會和
transform.position
或者transform.scale
沖突(同樣是使用關(guān)鍵路徑來做獨立的動畫屬性)。
transform.rotation
屬性有一個奇怪的問題是它其實并不存在辐棒。這是因為CATransform3D
并不是一個對象病曾,它實際上是一個結(jié)構(gòu)體牍蜂,也沒有符合KVC相關(guān)屬性,transform.rotation
實際上是一個 CALayer
用于處理動畫變換的虛 擬屬性泰涂。
你不可以直接設(shè)置 transform.rotation
或者 transform.scale
鲫竞,他們不能被直接使用。當(dāng)你對他們做動畫時逼蒙,Core Animation
自動地根據(jù)通過 CAValueFunction
來計算的值來更新 transform
屬性贡茅。
CAValueFunction
用于把我們賦給虛擬的transfrom.rotation
簡單浮點值轉(zhuǎn)換成真正的用于擺放圖層的CATransform3D
矩陣值。你可以通過設(shè)置CAPropertyAnimation
的 valueFunction
屬性來改變其做,于是你設(shè)置的函數(shù)將會覆蓋默認(rèn)的函數(shù)顶考。
CAValueFunction
看起來似乎是對那些不能簡單相加的屬性(例如變換矩陣)做動畫的非常有用的機制,但由于 CAValueFunction
的實現(xiàn)細(xì)節(jié)是私有的妖泄,所以目 前不能通過繼承它來自定義驹沿。你可以通過使用蘋果目前已經(jīng)提供的常量(目前都是 和變換矩陣的虛擬屬性相關(guān),所以沒太多使用場景了蹈胡,因為這些屬性都有了默認(rèn)的 實現(xiàn)方式)渊季。
動畫組
CABasicAnimation
和CAKeyframeAnimation
僅僅作用于單獨的屬性,而CAAnimationGroup
可以把這些動畫組合在一起罚渐。 CAAnimationGroup
是另一個繼承于CAAnimation
的子類却汉,它添加了一個 animations
數(shù)組的屬性,用來組合別的動畫荷并。
- (void)viewDidLoad {
[super viewDidLoad];
UIBezierPath *bezierPath = [[UIBezierPath alloc]init];
[bezierPath moveToPoint:CGPointMake(0, 150)];
[bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(200, 200) controlPoint2:CGPointMake(150, 150)];
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = bezierPath.CGPath;
pathLayer.fillColor = [UIColor clearColor].CGColor;
pathLayer.strokeColor = [UIColor redColor].CGColor;
pathLayer.lineWidth = 3.0f;
[self.containerView.layer addSublayer:pathLayer];
CALayer *colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(0, 0, 64, 64);
colorLayer.position = CGPointMake(0, 150);
colorLayer.backgroundColor = [UIColor greenColor].CGColor;
[self.containerView.layer addSublayer:colorLayer];
CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];
keyAnimation.keyPath = @"position";
keyAnimation.path = bezierPath.CGPath;
keyAnimation.rotationMode = kCAAnimationRotateAuto;
CABasicAnimation *baseAnimation = [CABasicAnimation animation];
baseAnimation.keyPath = @"backgroundColor";
baseAnimation.toValue = (__bridge id)[UIColor redColor].CGColor;
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[keyAnimation, baseAnimation];
groupAnimation.duration = 4.0;
groupAnimation.autoreverses = true;
[colorLayer addAnimation:groupAnimation forKey:nil];
}
過渡
有時候?qū)τ?code>iOS應(yīng)用程序來說合砂,希望能通過屬性動畫來對比較難做動畫的布局進(jìn)行 一些改變。比如交換一段文本和圖片源织,或者用一段網(wǎng)格視圖來替換翩伪,等等。屬性動畫只對圖層的可動畫屬性起作用谈息,所以如果要改變一個不能動畫的屬性(比如圖 片)缘屹,或者從層級關(guān)系中添加或者移除圖層,屬性動畫將不起作用侠仇。
于是就有了過渡的概念轻姿。過渡并不像屬性動畫那樣平滑地在兩個值之間做動畫,而是影響到整個圖層的變化逻炊。過渡動畫首先展示之前的圖層外觀互亮,然后通過一個交換過渡到新的外觀。
為了創(chuàng)建一個過渡動畫嗅骄,我們將使用CATransition
胳挎,同樣是另一個CAAnimation
的子類,和別的子類不同,CATransition
有一 個type
和 subtype
來標(biāo)識變換效果。type
屬性是一個NSString
類型,可以被設(shè)置成如下類型:
kCATransitionFade
kCATransitionMoveIn
kCATransitionPush
kCATransitionReveal
到目前為止你只能使用上述四種類型溺森,但你可以通過一些別的方法來自定義過渡效果慕爬,后續(xù)會詳細(xì)介紹窑眯。
默認(rèn)的過渡類型是 kCATransitionFade
,當(dāng)你在改變圖層屬性之后医窿,就創(chuàng)建了一 個平滑的淡入淡出效果磅甩。
我們在第七章的例子中就已經(jīng)用到過 kCATransitionPush
,它創(chuàng)建了一個新的圖 層姥卢,從邊緣的一側(cè)滑動進(jìn)來卷要,把舊圖層從另一側(cè)推出去的效果。
kCATransitionMoveIn
和 kCATransitionReveal
與 kCATransitionPush
類 似独榴,都實現(xiàn)了一個定向滑動的動畫僧叉,但是有一些細(xì)微的不同,kCATransitionMoveIn
從頂部滑動進(jìn)入棺榔,但不像推送動畫那樣把老土層推走瓶堕,然而kCATransitionReveal
把原始的圖層滑動出去來顯示新的外觀,而不是把新的圖層滑動進(jìn)入症歇。
后面三種過渡類型都有一個默認(rèn)的動畫方向郎笆,它們都從左側(cè)滑入,但是你可以通 過 subtype
來控制它們的方向忘晤,提供了如下四種類型:
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom
一個簡單的用CATransition
來對非動畫屬性做動畫的例子宛蚓,這里我們對UIImage
的image
屬性做修改,但是隱式動畫或者CAPropertyAnimation
都不能對它做動畫设塔,因為Core Animation
不知道如何在插圖圖片凄吏。通過對圖層應(yīng)用一個淡入淡出的過渡,我們可以忽略它的內(nèi)容來做平滑動畫壹置,我們來嘗試修改過渡的 type
常量來觀察其它效果竞思。
使用 CATransition
來對UIImageView
做動畫
- (void)viewDidLoad {
[super viewDidLoad];
self.images = @[
[UIImage imageNamed:@"Anchor.png"],
[UIImage imageNamed:@"Cone.png"],
[UIImage imageNamed:@"Igloo.png"],
[UIImage imageNamed:@"Spaceship.png"]
];
}
- (IBAction)changeImage:(id)sender {
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
[self.imageView.layer addAnimation:transition forKey:nil];
UIImage *currentImage = self.imageView.image;
NSUInteger index = [self.images indexOfObject:currentImage];
index = (index + 1) % [self.images count];
self.imageView.image = self.images[index];
}
效果如下:
你可以從代碼中看出表谊,過渡動畫和之前的屬性動畫或者動畫組添加到圖層上的方式一致钞护,都是通過- addAnimation: forKey:
方法。但是和屬性動畫不同的是爆办,對指定的圖層一次只能使用一次 CATransition
难咕,因此,無論你對動畫的鍵設(shè)置什么值距辆,過渡動畫都會對它的鍵設(shè)成“transition”
余佃,也就是常量 kCATransition 。
隱式過渡
CATransision
可以對圖層任何變化平滑過渡的事實使得它成為那些不好做動畫 的屬性圖層行為的理想候選跨算。蘋果當(dāng)然意識到了這點爆土,并且當(dāng)設(shè)置了 CALayer
的 content
屬性的時候, CATransition
的確是默認(rèn)的行為诸蚕。但是對于視圖關(guān)聯(lián)的圖層步势,或者是其他隱式動畫的行為氧猬,這個特性依然是被禁用的,但 是對于你自己創(chuàng)建的圖層坏瘩,這意味著對圖層contents
圖片做的改動都會自動附上 淡入淡出的動畫盅抚。
我們在第七章使用CATransition
作為一個圖層行為來改變圖層的背景色,當(dāng)然backgroundColor
屬性可以通過正常的CAPropertyAnimation
來實現(xiàn)倔矾,但這不是說不可以用CATransition
來實行妄均。
對圖層樹的動畫
CATransition
并不作用于指定的圖層屬性,這就是說你可以在即使不能準(zhǔn)確得 知改變了什么的情況下對圖層做動畫哪自,例如丰包,在不知道 UITableView
哪一行被添加或者刪除的情況下,直接就可以平滑地刷新它壤巷,或者在不知道 UIViewController
內(nèi)部的視圖層級的情況下對兩個不同的實例做過渡動畫烫沙。
這些例子和我們之前所討論的情況完全不同,因為它們不僅涉及到圖層的屬性隙笆,而且是整個圖層樹的改變--我們在這種動畫的過程中手動在層級關(guān)系中添加或者移除 圖層锌蓄。
這里用到了一個小詭計,要確保CATransition
添加到的圖層在過渡動畫發(fā)生時不會在樹狀結(jié)構(gòu)中被移除,否則CATransition
將會和圖層一起被移除. 一般來說,你只需要將動畫添加到被影響圖層的superlayer
.
我們展示了如何在 UITabBarController
切換標(biāo)簽的時候添加淡入淡出的動畫撑柔。這里我們建立了默認(rèn)的標(biāo)簽應(yīng)用程序模板瘸爽,然后用UITabBarControllerDelegate
的- tabBarController: deisSelectViewController:
方法來應(yīng)用過渡動畫。我們把動畫添加到UITabBarController
的視圖圖層上铅忿,于是在標(biāo)簽被替換的時候動畫不會被移除剪决。
@interface AppDelegate ()
@property (nonatomic, strong)UITabBarController *tabBarController;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
FirstViewController *first = [[FirstViewController alloc]init];
SecondViewController *second = [[SecondViewController alloc]init];
self.tabBarController = [[UITabBarController alloc]init];
self.tabBarController.viewControllers = @[first, second];
self.tabBarController.delegate = self;
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
[self.tabBarController.view.layer addAnimation:transition forKey:nil];
}
自定義動畫
我們證實了過渡是一種對那些不太好做平滑動畫屬性的強大工具,但是CATransition
的提供的動畫類型太少了檀训。
更奇怪的是蘋果通過UIView +transitionFromView:toView:duration:options:completion:
和 + transitiononWithView:duration:options:animations:
方法提供了Core Animation
的過渡特性柑潦。但是這里的可用的過渡選項和CATransition
的 type
屬性提供的常量完全不同。UIView
過渡方法中options
參數(shù)可以由如下常量指定:
UIViewAnimationOptionTransitionFlipFromLeft
UIViewAnimationOptionTransitionFlipFromRight
UIViewAnimationOptionTransitionCurlUp
UIViewAnimationOptionTransitionCurlDown
UIViewAnimationOptionTransitionCrossDissolve
UIViewAnimationOptionTransitionFlipFromTop
UIViewAnimationOptionTransitionFlipFromBottom
除了UIViewAnimationOptionTransitionCrossDissolve
之外峻凫,剩下的值和CATransition
類型完全沒關(guān)系渗鬼。
使用UIKit提供的方法來做過渡動畫
- (void)viewDidLoad {
[super viewDidLoad];
self.images = @[
[UIImage imageNamed:@"Anchor.png"],
[UIImage imageNamed:@"Cone.png"],
[UIImage imageNamed:@"Igloo.png"],
[UIImage imageNamed:@"Spaceship.png"]
];
}
- (IBAction)changeImage:(id)sender {
[UIView transitionWithView:self.imageView duration:1.0 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{
UIImage *currentImage = self.imageView.image;
NSUInteger index = [self.images indexOfObject:currentImage];
index = (index + 1) % [self.images count];
self.imageView.image = self.images[index];
} completion:^(BOOL finished) {
}];
}
文檔暗示過在iOS5
(帶來了Core Image
框架)之后,可以通過 CATransition
的 filter
屬性荧琼,用 CIFilter
來創(chuàng)建其它的過渡效果譬胎。然是 直到iOS6
都做不到這點。試圖對CATransition
使用Core Image
的濾鏡完全沒效果(但是在Mac OS
中是可行的命锄,也許文檔是想表達(dá)這個意思)堰乔。
因此,根據(jù)要實現(xiàn)的效果脐恩,你只用關(guān)心是用CATransition
還是用UIView
的過渡方法就可以了镐侯。希望下個版本的iOS
系統(tǒng)可以通過CATransition
很好的支持Core Image
的過渡濾鏡效果(或許甚至?xí)行碌姆椒?。
但這并不意味著在iOS
上就不能實現(xiàn)自定義的過渡效果了驶冒。這只是意味著你需要做 一些額外的工作苟翻。就像之前提到的那樣搭伤,過渡動畫做基礎(chǔ)的原則就是對原始的圖層 外觀截圖,然后添加一段動畫袜瞬,平滑過渡到圖層改變之后那個截圖的效果怜俐。如果我 們知道如何對圖層截圖,我們就可以使用屬性動畫來代替 CATransition
或者是 UIKit
的過渡方法來實現(xiàn)動畫邓尤。
事實證明拍鲤,對圖層做截圖還是很簡單的。CALayer
有一個- renderInContext:
方法汞扎,可以通過把它繪制到Core Graphics
的上下文中捕獲當(dāng) 前內(nèi)容的圖片季稳,然后在另外的視圖中顯示出來。如果我們把這個截屏視圖置于原始視圖之上澈魄,就可以遮住真實視圖的所有變化景鼠,于是重新創(chuàng)建了一個簡單的過渡效 果。
Demo 我們對當(dāng)前視圖狀態(tài)截圖痹扇,然后在我們改變原始 視圖的背景色的時候?qū)貓D快速轉(zhuǎn)動并且淡出铛漓,為了讓事情更簡單,我們用UIView - animateWithDuration: completion:
方法 來實現(xiàn)鲫构。雖然用 CABasicAnmation
可以達(dá)到同樣的效果浓恶,但是那樣的話我們就 需要對圖層的變換和不透明屬性創(chuàng)建單獨的動畫,然后當(dāng)動畫結(jié)束的時候在 CAAnimationDelegate
中把coverView
從屏幕中移除结笨。
這里有個警告:-renderInContext:
捕獲了圖層的圖片和子圖層包晰,但是不能對子圖層正確地處理變換效果,而且對視頻和OpenGL
內(nèi)容也不起作用炕吸。但是用 CATransition
伐憾,或者用私有的截屏方式就沒有這個限制了。
在動畫過程中取消動畫
之前提到過赫模,你可以用-addAnimation:forKey:
方法中的 key
參數(shù)來在添加動 畫之后檢索一個動畫树肃,使用如下方法:
- (CAAnimation *)animationForKey:(NSString *)key;
但并不支持在動畫運行過程中修改動畫,所以這個方法主要用來檢測動畫的屬性嘴瓤,或者判斷它是否被添加到當(dāng)前圖層中扫外。
為了終止一個指定的動畫,你可以用如下方法把它從圖層移除掉:
- (void)removeAnimationForKey:(NSString *)key;
或者移除所有動畫:
- (void)removeAllAnimations;
動畫一旦被移除廓脆,圖層的外觀就立刻更新到當(dāng)前的模型圖層的值。一般說來磁玉,動畫 在結(jié)束之后被自動移除停忿,除非設(shè)置 removedOnCompletion
為 NO
,如果你設(shè)置動 畫在結(jié)束之后不被自動移除蚊伞,那么當(dāng)它不需要的時候你要手動移除它;否則它會一 直存在于內(nèi)存中席赂,直到圖層被銷毀吮铭。
我們來擴(kuò)展之前旋轉(zhuǎn)飛船的示例,這里添加一個按鈕來停止或者啟動動畫颅停。這一次 我們用一個非 nil
的值作為動畫的鍵谓晌,以便之后可以移除它。 - animationDidStop:finished:
方法中的flag
參數(shù)表明了動畫是自然結(jié)束還是 被打斷癞揉,我們可以在控制臺打印出來纸肉。如果你用停止按鈕來終止動畫,它會打印NO
喊熟,如果允許它完成柏肪,它會打印 YES
。
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) CALayer *shipLayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.shipLayer = [CALayer layer];
self.shipLayer.frame = CGRectMake(0, 0, 128, 128);
self.shipLayer.position = CGPointMake(150, 150);
self.shipLayer.contents = (__bridge id)[UIImage imageNamed:@"Igloo.png"].CGImage;
[self.containerView.layer addSublayer:self.shipLayer];
}
- (IBAction)startAnimation:(id)sender {
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform.rotation";
animation.duration = 2.0;
animation.byValue = @(M_PI * 2);
animation.delegate = self;
[self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];
}
- (IBAction)stopAnimation:(id)sender {
[self.shipLayer removeAnimationForKey:@"rotateAnimation"];
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(@"The animation stopped(finished:%@)",flag ? @"YES" : @"NO");
}
總結(jié)
這一章中芥牌,我們涉及了屬性動畫(你可以對單獨的圖層屬性動畫有更加具體的控制)烦味,動畫組(把多個屬性動畫組合成一個獨立單元)以及過度(影響整個圖層,可以用來對圖層的任何內(nèi)容做任何類型的動畫壁拉,包括子圖層的添加和移除)谬俄。