# 過渡
有時候對于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`來對非動畫屬性做動畫的例子如清單8.11所示,這里我們對`UIImage`的`image`屬性做修改,但是隱式動畫或者`CAPropertyAnimation`都不能對它做動畫,因為Core Animation不知道如何在插圖圖片。通過對圖層應用一個淡入淡出的過渡,我們可以忽略它的內容來做平滑動畫(圖8.4),我們來嘗試修改過渡的`type`常量來觀察其它效果。
清單8.11 使用`CATransition`來對`UIImageView`做動畫
~~~
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *imageView;
@property (nonatomic, copy) NSArray *images;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//set up images
self.images = @[[UIImage imageNamed:@"Anchor.png"],
[UIImage imageNamed:@"Cone.png"],
[UIImage imageNamed:@"Igloo.png"],
[UIImage imageNamed:@"Spaceship.png"]];
}
- (IBAction)switchImage
{
//set up crossfade transition
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
//apply transition to imageview backing layer
[self.imageView.layer addAnimation:transition forKey:nil];
//cycle to next image
UIImage *currentImage = self.imageView.image;
NSUInteger index = [self.images indexOfObject:currentImage];
index = (index + 1) % [self.images count];
self.imageView.image = self.images[index];
}
@end
~~~
你可以從代碼中看出,過渡動畫和之前的屬性動畫或者動畫組添加到圖層上的方式一致,都是通過`-addAnimation:forKey:`方法。但是和屬性動畫不同的是,對指定的圖層一次只能使用一次`CATransition`,因此,無論你對動畫的鍵設置什么值,過渡動畫都會對它的鍵設置成“transition”,也就是常量`kCATransition`。

圖8.4 使用`CATransition`對圖像平滑淡入淡出
## 隱式過渡
`CATransision`可以對圖層任何變化平滑過渡的事實使得它成為那些不好做動畫的屬性圖層行為的理想候選。蘋果當然意識到了這點,并且當設置了`CALayer`的`content`屬性的時候,`CATransition`的確是默認的行為。但是對于視圖關聯的圖層,或者是其他隱式動畫的行為,這個特性依然是被禁用的,但是對于你自己創建的圖層,這意味著對圖層`contents`圖片做的改動都會自動附上淡入淡出的動畫。
我們在第七章使用`CATransition`作為一個圖層行為來改變圖層的背景色,當然`backgroundColor`屬性可以通過正常的`CAPropertyAnimation`來實現,但這不是說不可以用`CATransition`來實行。
## 對圖層樹的動畫
`CATransition`并不作用于指定的圖層屬性,這就是說你可以在即使不能準確得知改變了什么的情況下對圖層做動畫,例如,在不知道`UITableView`哪一行被添加或者刪除的情況下,直接就可以平滑地刷新它,或者在不知道`UIViewController`內部的視圖層級的情況下對兩個不同的實例做過渡動畫。
這些例子和我們之前所討論的情況完全不同,因為它們不僅涉及到圖層的屬性,而且是整個*圖層樹*的改變--我們在這種動畫的過程中手動在層級關系中添加或者移除圖層。
這里用到了一個小詭計,要確保`CATransition`添加到的圖層在過渡動畫發生時不會在樹狀結構中被移除,否則`CATransition`將會和圖層一起被移除。一般來說,你只需要將動畫添加到被影響圖層的`superlayer`。
在清單8.2中,我們展示了如何在`UITabBarController`切換標簽的時候添加淡入淡出的動畫。這里我們建立了默認的標簽應用程序模板,然后用`UITabBarControllerDelegate`的`-tabBarController:didSelectViewController:`方法來應用過渡動畫。我們把動畫添加到`UITabBarController`的視圖圖層上,于是在標簽被替換的時候動畫不會被移除。
清單8.12 對`UITabBarController`做動畫
~~~
#import "AppDelegate.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#import
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
UIViewController *viewController1 = [[FirstViewController alloc] init];
UIViewController *viewController2 = [[SecondViewController alloc] init];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = @[viewController1, viewController2];
self.tabBarController.delegate = self;
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
?//set up crossfade transition
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
//apply transition to tab bar controller's view
[self.tabBarController.view.layer addAnimation:transition forKey:nil];
}
@end
~~~
## 自定義動畫
我們證實了過渡是一種對那些不太好做平滑動畫屬性的強大工具,但是`CATransition`的提供的動畫類型太少了。
更奇怪的是蘋果通過`UIView +transitionFromView:toView:duration:options:completion:`和`+transitionWithView:duration:options:animations:`方法提供了Core Animation的過渡特性。但是這里的可用的過渡選項和`CATransition`的`type`屬性提供的常量*完全不同*。`UIView`過渡方法中`options`參數可以由如下常量指定:
~~~
UIViewAnimationOptionTransitionFlipFromLeft
~~~
UIViewAnimationOptionTransitionFlipFromRight UIViewAnimationOptionTransitionCurlUp UIViewAnimationOptionTransitionCurlDown UIViewAnimationOptionTransitionCrossDissolve UIViewAnimationOptionTransitionFlipFromTop UIViewAnimationOptionTransitionFlipFromBottom
除了`UIViewAnimationOptionTransitionCrossDissolve`之外,剩下的值和`CATransition`類型完全沒關系。你可以用之前例子修改過的版本來測試一下(見清單8.13)。
清單8.13 使用UIKit提供的方法來做過渡動畫
~~~
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *imageView;
@property (nonatomic, copy) NSArray *images;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad]; //set up images
self.images = @[[UIImage imageNamed:@"Anchor.png"],
[UIImage imageNamed:@"Cone.png"],
[UIImage imageNamed:@"Igloo.png"],
[UIImage imageNamed:@"Spaceship.png"]];
- (IBAction)switchImage
{
[UIView transitionWithView:self.imageView duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
//cycle to next image
UIImage *currentImage = self.imageView.image;
NSUInteger index = [self.images indexOfObject:currentImage];
index = (index + 1) % [self.images count];
self.imageView.image = self.images[index];
}
completion:NULL];
}
@end
~~~
文檔暗示過在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的上下文中捕獲當前內容的圖片,然后在另外的視圖中顯示出來。如果我們把這個截屏視圖置于原始視圖之上,就可以遮住真實視圖的所有變化,于是重新創建了一個簡單的過渡效果。
清單8.14演示了一個基本的實現。我們對當前視圖狀態截圖,然后在我們改變原始視圖的背景色的時候對截圖快速轉動并且淡出,圖8.5展示了我們自定義的過渡效果。
為了讓事情更簡單,我們用`UIView -animateWithDuration:completion:`方法來實現。雖然用`CABasicAnimation`可以達到同樣的效果,但是那樣的話我們就需要對圖層的變換和不透明屬性創建單獨的動畫,然后當動畫結束的是哦戶在`CAAnimationDelegate`中把`coverView`從屏幕中移除。
清單8.14 用`renderInContext:`創建自定義過渡效果
~~~
@implementation ViewController
- (IBAction)performTransition
{
//preserve the current view snapshot
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();
//insert snapshot view in front of this one
UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];
coverView.frame = self.view.bounds;
[self.view addSubview:coverView];
//update the view (we'll simply randomize the layer background color)
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.view.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
//perform animation (anything you like)
[UIView animateWithDuration:1.0 animations:^{
//scale, rotate and fade the view
CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 0.01);
transform = CGAffineTransformRotate(transform, M_PI_2);
coverView.transform = transform;
coverView.alpha = 0.0;
} completion:^(BOOL finished) {
//remove the cover view now we're finished with it
[coverView removeFromSuperview];
}];
}
@end
~~~

圖8.5 使用`renderInContext:`創建自定義過渡效果
這里有個警告:`-renderInContext:`捕獲了圖層的圖片和子圖層,但是不能對子圖層正確地處理變換效果,而且對視頻和OpenGL內容也不起作用。但是用`CATransition`,或者用私有的截屏方式就沒有這個限制了。
- Introduction
- 1. 圖層樹
- 1.1 圖層與視圖
- 1.2 圖層的能力
- 1.3 使用圖層
- 1.4 總結
- 2. 寄宿圖
- 2.1 contents屬性
- 2.2 Custom Drawing
- 2.3 總結
- 3. 圖層幾何學
- 3.1 布局
- 3.2 錨點
- 3.3 坐標系
- 3.4 Hit Testing
- 3.5 自動布局
- 3.6 總結
- 4. 視覺效果
- 4.1 圓角
- 4.2 圖層邊框
- 4.3 陰影
- 4.4 圖層蒙板
- 4.5 拉伸過濾
- 4.6 組透明
- 4.7 總結
- 5. 變換
- 5.1 仿射變換
- 5.2 3D變換
- 5.3 固體對象
- 5.4 總結
- 6. 專用圖層
- 6.1 CAShapeLayer
- 6.2 CATextLayer
- 6.3 CATransformLayer
- 6.4 CAGradientLayer
- 6.5 CAReplicatorLayer
- 6.6 CAScrollLayer
- 6.7 CATiledLayer
- 6.8 CAEmitterLayer
- 6.9 CAEAGLLayer
- 6.10 AVPlayerLayer
- 6.11 總結
- 7. 隱式動畫
- 7.1 事務
- 7.2 完成塊
- 7.3 圖層行為
- 7.4 呈現與模型
- 7.5 總結
- 8. 顯式動畫
- 8.1 屬性動畫
- 8.2 動畫組
- 8.3 過渡
- 8.4 在動畫過程中取消動畫
- 8.5 總結
- 9. 圖層時間
- 9.1 CAMediaTiming協議
- 9.2 層級關系時間
- 9.3 手動動畫
- 9.4 總結
- 10. 緩沖
- 10.1 動畫速度
- 10.2 自定義緩沖函數
- 10.3 總結
- 11. 基于定時器的動畫
- 11.1 定時幀
- 11.2 物理模擬
- 12. 性能調優
- 12.1. CPU VS GPU
- 12.2 測量,而不是猜測
- 12.3 Instruments
- 12.4 總結
- 13. 高效繪圖
- 13.1 軟件繪圖
- 13.2 矢量圖形
- 13.3 臟矩形
- 13.4 異步繪制
- 13.5 總結
- 14. 圖像IO
- 14.1 加載和潛伏
- 14.2 緩存
- 14.3 文件格式
- 14.4 總結
- 15. 圖層性能
- 15.1 隱式繪制
- 15.2 離屏渲染
- 15.3 混合和過度繪制
- 15.4 減少圖層數量
- 15.5 總結