上一章介紹了隱式動畫的概念。隱式動畫是在iOS
平臺創建動態用戶界面的一種直接方式,也是UIKit
動畫機制的基礎,不過它并不能涵蓋所有的動畫類型。在這一章 中,我們將要研究一下顯式動畫,它能夠對一些屬性做指定的自定義動畫,或者創建非線性動畫,比如沿著任意一條曲線移動。
屬性動畫
CAAnimationDelegate
在任何頭文件中都找不到,但是可以在CAAnimation
頭文件或者蘋果開發者文檔中找到相關函數。在這個例子中,我們用- animationDidStop: finished:
方法在動畫結束之后來更新圖層backgroundColor
的。
當更新屬性的時候,我們需要設置一個新的事務,并且禁用圖層行為。否則動畫會發生兩次,一個是因為顯式的 CABasicAnimation
,另一次是因為隱式動畫,具體實現代碼如下。
- (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
而言,使用委托模式而不是一個完成塊會帶來一個問題,就是當 你有多個動畫的時候,無法在在回調方法中區分。在一個視圖控制器中創建動畫的 時候,通常會用控制器本身作為一個委托(如上面所示),但是所有的動畫都會調用同一個回調方法,所以你就需要判斷到底是那個圖層的調用。
動畫本身會作為一個參數傳入委托的方法,也許你會認為可以控制器中把動畫存儲
為一個屬性,然后在回調用比較,但實際上并不起作用,因為委托傳入的動畫參數
是原始值的一個深拷貝,從而不是同一個值。
當使用-animation:forKey:
把動畫添加到圖層, 這里有一個到目前為止我們都設置為nil
的key
參數。這里的鍵是-animationForKey:
方法找到對應動 畫的唯一標識符,而當前動畫的所有鍵都可以用animationKeys
獲取。如果我們 對每個動畫都關聯一個唯一的鍵,就可以對每個圖層循環所有鍵,然后調用 - animationForKey:
來比對結果。盡管這不是一個優雅的實現。
幸運的是,還有一種更加簡單的方法。像所有的 NSObject
子類一 樣,CAAnimation
實現了KVC
(鍵-值-編碼)協議,于是你可以用 - setValue:forKey:
和- valueForKey:
方法來存取屬性。但是CAAnimation
有 一個不同的性能:它更像一個NSDictionary
,可以讓你隨意設置鍵值對,即使和你使用的動畫類所聲明的屬性并不匹配。
這意味著你可以對動畫用任意類型打標簽。在這里,我們給UIView
類型的指針添 加的動畫,所以可以簡單地判斷動畫到底屬于哪個視圖,然后在委托方法中用這個 信息正確地更新鐘的指針。
在模擬器上運行的很好,但當真 正跑在iOS設備上時,我們發現在 -animationDidStop:finished: 委托方法調用 之前,指針會迅速返回到原始值。
問題在于回調方法在動畫完成之前已經被調用了,但不能保證這發生在屬性動畫返
回初始狀態之前。這同時也很好地說明了為什么要在真實的設備上測試動畫代碼,
而不僅僅是模擬器。
我們可以用一個 fillMode
屬性來解決這個問題,下一章會詳細說明,這里知道在 動畫之前設置它比在動畫結束之后更新屬性更加方便。
關鍵幀動畫
CABasicAnimation
揭示了大多數隱式動畫背后依賴的機制,這的確很有趣,但是顯示地給圖層添加CABasicAnimation
相較于隱式動畫而言,只能說費力不討好。
CAKeyframeAnimation
是另一種UIKit
沒有暴露出來但功能強大的類。和CABasicAnimation
類似, CAKeyframeAnimation
同樣是CAPropertyAnimation
的一個子類,它依然作用于單一的一個屬性,但是和CABasicAnimation
不一樣的是,它不限制于設置一個起始和結束的值,而是可以根據一連串隨意的值來做動畫。
關鍵幀起源于傳動動畫,意思是指主導的動畫在顯著改變發生時重繪當前幀(也就 是關鍵幀),每幀之間剩下的繪制(可以通過關鍵幀推算出)將由熟練的藝術家來 完成。 CAKeyframeAnimation
也是同樣的道理:你提供了顯著的幀,然后Core Animation
在每幀之間進行插入。
我們可以用之前使用顏色圖層的例子來演示,設置一個顏色的數組,然后通過關鍵 幀動畫播放出來
- (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];
}
注意到序列中開始和結束的顏色都是藍色,這是因為CAKeyframeAnimation
并不 能自動把當前值作為第一幀(就像CABasicAnimation
那樣把fromValue
設為 nil )。動畫會在開始的時候突然跳轉到第一幀的值,然后在動畫結束的時候 突然恢復到原始的值。所以為了動畫的平滑特性,我們需要開始和結束的關鍵幀來 匹配當前屬性的值。
當然可以創建一個結束和開始值不同的動畫,那樣的話就需要在動畫啟動之前手動更新屬性和最后一幀的值保持一致,就和之前討論的一樣。
我們用duration
屬性把動畫時間從默認的0.25
秒增加到2
秒,以便于動畫做的不 那么快。運行它,你會發現動畫通過顏色不斷循環,但效果看起來有些奇怪。原因 在于動畫以一個恒定的步調在運行。當在每個動畫之間過渡的時候并沒有減速,這 就產生了一個略微奇怪的效果,為了讓動畫看起來更自然,我們需要調整一下緩 沖,第十章將會詳細說明。
提供一個數組的值就可以按照顏色變化做動畫,但一般來說用數組來描述動畫運動并不直觀。
CAKeyframeAnimation
有另一種方式去指定動畫,就是使用CGPath
。path
屬性可以用一種直觀的方式,使用Core Graphics
函數定義運動序列來繪制動畫。
我們來用一個宇宙飛船沿著一個簡單曲線的實例演示一下。為了創建路徑,我們需要使用一個三次貝塞爾曲線,它是一種使用開始點,結束點和另外兩個控制點來定義形狀的曲線,可以通過使用一個基于C
的Core Graphics
繪圖指令來創建,不過用UIKit
提供的 UIBezierPath
類會更簡單。
我們這次用CAShapeLayer
來在屏幕上繪制曲線,盡管對動畫來說并不是必須 的,但這會讓我們的動畫更加形象。繪制完 CGPath
之后,我們用它來創建一 個CAKeyframeAnimation
,然后用它來應用到我們的宇宙飛船。
運行示例,你會發現飛船的動畫有些不太真實,這是因為當它運動的時候永遠指向 右邊,而不是指向曲線切線的方向。你可以調整它的 affineTransform
來對運動 方向做動畫,但很可能和其它的動畫沖突。
蘋果預見到了這點,并且給CAKeyFrameAnimation
添加了一個rotationMode
的屬性。設置它為常量KCAAnimationRotateAuto
,圖層將會根據曲線的切線自動旋轉。
- (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];
}
虛擬屬性
之前提到過屬性動畫實際上是針對于關鍵路徑而不是一個鍵,這就意味著可以對子屬性甚至是虛擬屬性做動畫。但是虛擬屬性到底是什么呢?
考慮一個旋轉的動畫:如果想要對一個物體做旋轉的動畫,那就需要作用
于transform
屬性,因為 CALayer
沒有顯式提供角度或者方向之類的屬性,代 碼如下所示
用 transform.rotation
而不是 transform
做動畫的好處 如下:
- 我們可以不通過關鍵幀一步旋轉多于180度的動畫。
- 可以用相對值而不是絕對值旋轉(設置
byValue
而不是toValue
)。 - 可以不用創建
CATransform3D
,而是使用一個簡單的數值來指定角度。 - 不會和
transform.position
或者transform.scale
沖突(同樣是使用關鍵路徑來做獨立的動畫屬性)。
transform.rotation
屬性有一個奇怪的問題是它其實并不存在。這是因為CATransform3D
并不是一個對象,它實際上是一個結構體,也沒有符合KVC相關屬性,transform.rotation
實際上是一個 CALayer
用于處理動畫變換的虛 擬屬性。
你不可以直接設置 transform.rotation
或者 transform.scale
,他們不能被直接使用。當你對他們做動畫時,Core Animation
自動地根據通過 CAValueFunction
來計算的值來更新 transform
屬性。
CAValueFunction
用于把我們賦給虛擬的transfrom.rotation
簡單浮點值轉換成真正的用于擺放圖層的CATransform3D
矩陣值。你可以通過設置CAPropertyAnimation
的 valueFunction
屬性來改變,于是你設置的函數將會覆蓋默認的函數。
CAValueFunction
看起來似乎是對那些不能簡單相加的屬性(例如變換矩陣)做動畫的非常有用的機制,但由于 CAValueFunction
的實現細節是私有的,所以目 前不能通過繼承它來自定義。你可以通過使用蘋果目前已經提供的常量(目前都是 和變換矩陣的虛擬屬性相關,所以沒太多使用場景了,因為這些屬性都有了默認的 實現方式)。
動畫組
CABasicAnimation
和CAKeyframeAnimation
僅僅作用于單獨的屬性,而CAAnimationGroup
可以把這些動畫組合在一起。 CAAnimationGroup
是另一個繼承于CAAnimation
的子類,它添加了一個 animations
數組的屬性,用來組合別的動畫。
- (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];
}
過渡
有時候對于iOS
應用程序來說,希望能通過屬性動畫來對比較難做動畫的布局進行 一些改變。比如交換一段文本和圖片,或者用一段網格視圖來替換,等等。屬性動畫只對圖層的可動畫屬性起作用,所以如果要改變一個不能動畫的屬性(比如圖 片),或者從層級關系中添加或者移除圖層,屬性動畫將不起作用。
于是就有了過渡的概念。過渡并不像屬性動畫那樣平滑地在兩個值之間做動畫,而是影響到整個圖層的變化。過渡動畫首先展示之前的圖層外觀,然后通過一個交換過渡到新的外觀。
為了創建一個過渡動畫,我們將使用CATransition
,同樣是另一個CAAnimation
的子類,和別的子類不同,CATransition
有一 個type
和 subtype
來標識變換效果。type
屬性是一個NSString
類型,可以被設置成如下類型:
kCATransitionFade
kCATransitionMoveIn
kCATransitionPush
kCATransitionReveal
到目前為止你只能使用上述四種類型,但你可以通過一些別的方法來自定義過渡效果,后續會詳細介紹。
默認的過渡類型是 kCATransitionFade
,當你在改變圖層屬性之后,就創建了一 個平滑的淡入淡出效果。
我們在第七章的例子中就已經用到過 kCATransitionPush
,它創建了一個新的圖 層,從邊緣的一側滑動進來,把舊圖層從另一側推出去的效果。
kCATransitionMoveIn
和 kCATransitionReveal
與 kCATransitionPush
類 似,都實現了一個定向滑動的動畫,但是有一些細微的不同,kCATransitionMoveIn
從頂部滑動進入,但不像推送動畫那樣把老土層推走,然而kCATransitionReveal
把原始的圖層滑動出去來顯示新的外觀,而不是把新的圖層滑動進入。
后面三種過渡類型都有一個默認的動畫方向,它們都從左側滑入,但是你可以通 過 subtype
來控制它們的方向,提供了如下四種類型:
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom
一個簡單的用CATransition
來對非動畫屬性做動畫的例子,這里我們對UIImage
的image
屬性做修改,但是隱式動畫或者CAPropertyAnimation
都不能對它做動畫,因為Core Animation
不知道如何在插圖圖片。通過對圖層應用一個淡入淡出的過渡,我們可以忽略它的內容來做平滑動畫,我們來嘗試修改過渡的 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
,因此,無論你對動畫的鍵設置什么值,過渡動畫都會對它的鍵設成“transition”
,也就是常量 kCATransition 。
隱式過渡
CATransision
可以對圖層任何變化平滑過渡的事實使得它成為那些不好做動畫 的屬性圖層行為的理想候選。蘋果當然意識到了這點,并且當設置了 CALayer
的 content
屬性的時候, CATransition
的確是默認的行為。但是對于視圖關聯的圖層,或者是其他隱式動畫的行為,這個特性依然是被禁用的,但 是對于你自己創建的圖層,這意味著對圖層contents
圖片做的改動都會自動附上 淡入淡出的動畫。
我們在第七章使用CATransition
作為一個圖層行為來改變圖層的背景色,當然backgroundColor
屬性可以通過正常的CAPropertyAnimation
來實現,但這不是說不可以用CATransition
來實行。
對圖層樹的動畫
CATransition
并不作用于指定的圖層屬性,這就是說你可以在即使不能準確得 知改變了什么的情況下對圖層做動畫,例如,在不知道 UITableView
哪一行被添加或者刪除的情況下,直接就可以平滑地刷新它,或者在不知道 UIViewController
內部的視圖層級的情況下對兩個不同的實例做過渡動畫。
這些例子和我們之前所討論的情況完全不同,因為它們不僅涉及到圖層的屬性,而且是整個圖層樹的改變--我們在這種動畫的過程中手動在層級關系中添加或者移除 圖層。
這里用到了一個小詭計,要確保CATransition
添加到的圖層在過渡動畫發生時不會在樹狀結構中被移除,否則CATransition
將會和圖層一起被移除. 一般來說,你只需要將動畫添加到被影響圖層的superlayer
.
我們展示了如何在 UITabBarController
切換標簽的時候添加淡入淡出的動畫。這里我們建立了默認的標簽應用程序模板,然后用UITabBarControllerDelegate
的- tabBarController: deisSelectViewController:
方法來應用過渡動畫。我們把動畫添加到UITabBarController
的視圖圖層上,于是在標簽被替換的時候動畫不會被移除。
@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
參數可以由如下常量指定:
UIViewAnimationOptionTransitionFlipFromLeft
UIViewAnimationOptionTransitionFlipFromRight
UIViewAnimationOptionTransitionCurlUp
UIViewAnimationOptionTransitionCurlDown
UIViewAnimationOptionTransitionCrossDissolve
UIViewAnimationOptionTransitionFlipFromTop
UIViewAnimationOptionTransitionFlipFromBottom
除了UIViewAnimationOptionTransitionCrossDissolve
之外,剩下的值和CATransition
類型完全沒關系。
使用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
來創建其它的過渡效果。然是 直到iOS6
都做不到這點。試圖對CATransition
使用Core Image
的濾鏡完全沒效果(但是在Mac OS
中是可行的,也許文檔是想表達這個意思)。
因此,根據要實現的效果,你只用關心是用CATransition
還是用UIView
的過渡方法就可以了。希望下個版本的iOS
系統可以通過CATransition
很好的支持Core Image
的過渡濾鏡效果(或許甚至會有新的方法)。
但這并不意味著在iOS
上就不能實現自定義的過渡效果了。這只是意味著你需要做 一些額外的工作。就像之前提到的那樣,過渡動畫做基礎的原則就是對原始的圖層 外觀截圖,然后添加一段動畫,平滑過渡到圖層改變之后那個截圖的效果。如果我 們知道如何對圖層截圖,我們就可以使用屬性動畫來代替 CATransition
或者是 UIKit
的過渡方法來實現動畫。
事實證明,對圖層做截圖還是很簡單的。CALayer
有一個- renderInContext:
方法,可以通過把它繪制到Core Graphics
的上下文中捕獲當 前內容的圖片,然后在另外的視圖中顯示出來。如果我們把這個截屏視圖置于原始視圖之上,就可以遮住真實視圖的所有變化,于是重新創建了一個簡單的過渡效 果。
Demo 我們對當前視圖狀態截圖,然后在我們改變原始 視圖的背景色的時候對截圖快速轉動并且淡出,為了讓事情更簡單,我們用UIView - animateWithDuration: completion:
方法 來實現。雖然用 CABasicAnmation
可以達到同樣的效果,但是那樣的話我們就 需要對圖層的變換和不透明屬性創建單獨的動畫,然后當動畫結束的時候在 CAAnimationDelegate
中把coverView
從屏幕中移除。
這里有個警告:-renderInContext:
捕獲了圖層的圖片和子圖層,但是不能對子圖層正確地處理變換效果,而且對視頻和OpenGL
內容也不起作用。但是用 CATransition
,或者用私有的截屏方式就沒有這個限制了。
在動畫過程中取消動畫
之前提到過,你可以用-addAnimation:forKey:
方法中的 key
參數來在添加動 畫之后檢索一個動畫,使用如下方法:
- (CAAnimation *)animationForKey:(NSString *)key;
但并不支持在動畫運行過程中修改動畫,所以這個方法主要用來檢測動畫的屬性,或者判斷它是否被添加到當前圖層中。
為了終止一個指定的動畫,你可以用如下方法把它從圖層移除掉:
- (void)removeAnimationForKey:(NSString *)key;
或者移除所有動畫:
- (void)removeAllAnimations;
動畫一旦被移除,圖層的外觀就立刻更新到當前的模型圖層的值。一般說來,動畫 在結束之后被自動移除,除非設置 removedOnCompletion
為 NO
,如果你設置動 畫在結束之后不被自動移除,那么當它不需要的時候你要手動移除它;否則它會一 直存在于內存中,直到圖層被銷毀。
我們來擴展之前旋轉飛船的示例,這里添加一個按鈕來停止或者啟動動畫。這一次 我們用一個非 nil
的值作為動畫的鍵,以便之后可以移除它。 - animationDidStop:finished:
方法中的flag
參數表明了動畫是自然結束還是 被打斷,我們可以在控制臺打印出來。如果你用停止按鈕來終止動畫,它會打印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");
}
總結
這一章中,我們涉及了屬性動畫(你可以對單獨的圖層屬性動畫有更加具體的控制),動畫組(把多個屬性動畫組合成一個獨立單元)以及過度(影響整個圖層,可以用來對圖層的任何內容做任何類型的動畫,包括子圖層的添加和移除)。