CATransition并不做用于指定的图层属性,这就是说你能够在即便不能准确得知改变了什么的状况下对图层作动画,例如,在不知道UITableView哪一行被添加或者删除的状况下,直接就能够平滑地刷新它,或者在不知道UIViewController内部的视图层级的状况下对两个不一样的实例作过渡动画。 git
这些例子和咱们以前所讨论的状况彻底不一样,由于它们不只涉及到图层的属性,并且是整个图层树的改变--咱们在这种动画的过程当中手动在层级关系中添加或者移除图层。 github
这里用到了一个小诡计,要确保CATransition添加到的图层在过渡动画发生时不会在树状结构中被移除,不然CATransition将会和图层一块儿被移除。通常来讲,你只须要将动画添加到被影响图层的superlayer。 app
在清单8.2中,咱们展现了如何在UITabBarController切换标签的时候添加淡入淡出的动画。这里咱们创建了默认的标签应用程序模板,而后用UITabBarControllerDelegate的 -tabBarController:didSelectViewController: 方法来应用过渡动画。咱们把动画添加到UITabBarController的视图图层上,因而在标签被替换的时候动画不会被移除。 框架
清单8.12 对UITabBarController作动画 dom
#import "AppDelegate.h" #import "FirstViewController.h" #import "SecondViewController.h" #import <QuartzCore/QuartzCore.h> @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中是可行的,也许文档是想表达这个意思)。 this
所以,根据要实现的效果,你只用关心是用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,或者用私有的截屏方式就没有这个限制了。
以前提到过,你能够用 -addAnimation:forKey: 方法中的key参数来在添加动画以后检索一个动画,使用以下方法:
- (CAAnimation *)animationForKey:(NSString *)key;
但并不支持在动画运行过程当中修改动画,因此这个方法主要用来检测动画的属性,或者判断它是否被添加到当前图层中。
为了终止一个指定的动画,你能够用以下方法把它从图层移除掉:
- (void)removeAnimationForKey:(NSString *)key;
或者移除全部动画:
- (void)removeAllAnimations;
动画一旦被移除,图层的外观就马上更新到当前的模型图层的值。通常说来,动画在结束以后被自动移除,除非设置removedOnCompletion为NO,若是你设置动画在结束以后不被自动移除,那么当它不须要的时候你要手动移除它;不然它会一直存在于内存中,直到图层被销毁。
咱们来扩展以前旋转飞船的示例,这里添加一个按钮来中止或者启动动画。这一次咱们用一个非nil的值做为动画的键,以便以后能够移除它。 -animationDidStop:finished: 方法中的flag参数代表了动画是天然结束仍是被打断,咱们能够在控制台打印出来。若是你用中止按钮来终止动画,它会打印NO,若是容许它完成,它会打印YES。
清单8.15是更新后的示例代码,图8.6显示告终果。
清单8.15 开始和中止一个动画
@interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @property (nonatomic, strong) CALayer *shipLayer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //add the ship 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: @"Ship.png"].CGImage; [self.containerView.layer addSublayer:self.shipLayer]; } - (IBAction)start { //animate the ship rotation 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)stop { [self.shipLayer removeAnimationForKey:@"rotateAnimation"]; } - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { //log that the animation stopped NSLog(@"The animation stopped (finished: %@)", flag? @"YES": @"NO"); } @end
图8.6 经过开始和中止按钮控制的旋转动画
这一章中,咱们涉及了属性动画(你能够对单独的图层属性动画有更加具体的控制),动画组(把多个属性动画组合成一个独立单元)以及过分(影响整个图层,能够用来对图层的任何内容作任何类型的动画,包括子图层的添加和移除)。
在第九章中,咱们继续学习CAMediaTiming协议,来看一看Core Animation是怎样处理逝去的时间。