1.CATransaction并发
事务;app
UIView有两个方法,+beginAnimations:context:和+commitAnimations,和CATransaction的+begin 和+commit方法相似。实际上在+beginAnimations:context:和+commitAnimations之间全部视图或者图层属性的改变而作的动画都是因为设置了CATransaction的缘由。dom
UIView封装动画中的块动画,对作一堆的属性动画在语法上会更加简单,但实质上它们都是在作一样的事情。ide
基于UIView的block的动画容许你在动画结束的时候提供一个完成的动做。CATranscation接口提供的+setCompletionBlock:方法也有一样的功能函数
2.属性动画实质:测试
当CALayer的属性被修改时候,它会调用-actionForKey:方法,传递属性的名称。剩下的操做都在CALayer的头文件中有详细的说明,实质上是以下几步:动画
图层首先检测它是否有委托,而且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法。若是有,直接调用并返回结果。this
若是没有委托,或者委托没有实现-actionForLayer:forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。atom
若是actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。url
最后,若是在style里面也找不到对应的行为,那么图层将会直接调用定义了每一个属性的标准行为的-defaultActionForKey:方法。
因此一轮完整的搜索结束以后,-actionForKey:要么返回空(这种状况下将不会有动画发生),要么是CAAction协议对应的对象,最后CALayer拿这个结果去对先前和当前的值作动画。
经过下面一段代码,能够理解每一个UIView对它关联的图层是如何禁用隐式动画的
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"Outside: %@", [self.view actionForLayer:self.view.layer forKey:@"backgroundColor"]); [UIView beginAnimations:nil context:nil]; NSLog(@"Inside: %@", [self.view actionForLayer:self.view.layer forKey:@"backgroundColor"]); [UIView commitAnimations]; }
运行结果:
2016-05-30 14:48:18.614 测试[10222:147396] Outside: <null> 2016-05-30 14:48:18.615 测试[10222:147396] Inside: <CABasicAnimation: 0x7f8f02403d10>
总结一下,咱们知道了以下几点
UIView 关联的图层禁用了隐式动画,对这种图层作动画的惟一办法就是使用UIView的动画函数(而不是依赖CATransaction),或者继承 UIView,并覆盖-actionForLayer:forKey:方法,或者直接建立一个显式动画(具体细节见第八章)。
对于单独存在的图层,咱们能够经过实现图层的-actionForLayer:forKey:委托方法,或者提供一个actions字典来控制隐式动画。
3.CATransition
CATransition继承CAAnimation,而CAAnimation响应CAAction协议;
CATransition *transition = [CATransition animation]; transition.type = kCATransitionPush; transition.subtype = kCATransitionFromLeft; self.colorLayer.actions = @{@"backgroundColor": transition};
这种方法,只是针对单独的图层,不能用在UIView关联的图层;
4.presentationLayer
每一个图层属性的显示值都被存储在一个叫作呈现图层的独立图层当中,他能够经过-presentationLayer方法来访问。这个呈现图层其实是模型图层的复制,可是它的属性值表明了在任何指定时刻当前外观效果。换句话说,你能够经过呈现图层的值来获取当前屏幕上真正显示出来的值
注意呈现图层仅仅当图层首次被提交(就是首次第一次在屏幕上显示)的时候建立,因此在那以前调用-presentationLayer将会返回nil。
在呈现图层上调用–modelLayer将会返回它正在呈现所依赖的CALayer。一般在一个图层上调用-modelLayer会返回–self(实际上咱们已经建立的原始图层就是一种数据模型)。
5.显式动画
在iOS中核心动画分为几类:基础动画、关键帧动画、动画组、转场动画。各个类的关系大体以下:
CAAnimation:核心动画的基础类,不能直接使用,负责动画运行时间、速度的控制。CAAnimation自己并无作多少工做,它提供了一个计时函数,一个委托(用于反馈动画状态)以及一个 removedOnCompletion,用于标识动画是否该在结束后自动释放(默认YES,为了防止内存泄露)。CAAnimation同时实现了一些 协议,包括CAAction(容许CAAnimation的子类能够提供图层行为),以及CAMediaTiming
CAPropertyAnimation:属性动画的基类(经过属性进行动画设置,注意是可动画属性),不能直接使用。经过指定动画的keyPath做用于一个单一属性,CAAnimation一般应用于一个指定的CALayer,因而这里指的也就是一个图层的 keyPath了。实际上它是一个关键路径(一些用点表示法能够在层级关系中指向任意嵌套的对象),而不只仅是一个属性的名称,由于这意味着动画不只能够 做用于图层自己的属性,并且还包含了它的子成员的属性,甚至是一些虚拟的属性
CAAnimationGroup:动画组,动画组是一种组合模式设计,能够经过动画组来进行全部动画行为的统一控制,组中全部动画效果能够并发执行。
CATransition:过渡(转场)动画,主要经过滤镜进行动画效果设置。过渡并不像属性动画那样平滑地在两个值之间作动画,而是影响到整个图层的变化。过渡动画首先展现以前的图层外观,而后经过一个交换过渡到新的外观。
CABasicAnimation:基础动画,经过属性修改进行动画参数控制,只有初始状态和结束状态。
CAKeyframeAnimation:关键帧动画,一样是经过属性进行动画参数控制,可是同基础动画不一样的是它能够有多个状态控制。CAKeyFrameAnimation添加了一个rotationMode的属性。设置它为常量kCAAnimationRotateAuto,图层将会根据曲线的切线自动旋转
基础动画、关键帧动画都属于属性动画,就是经过修改属性值产生动画效果,开发人员只须要设置初始值和结束值,中间的过程动画(又叫“补间动画”)由 系统自动计算产生。和基础动画不一样的是关键帧动画能够设置多个属性值,每两个属性中间的补间动画由系统自动完成,所以从这个角度而言基础动画又能够当作是 有两个关键帧的关键帧动画。
- (void)applyBasicAnimation:(CABasicAnimation *)animation toLayer:(CALayer *)layer { //set the from value (using presentation layer if available) animation.fromValue = [layer.presentationLayer ?: layer valueForKeyPath:animation.keyPath]; //update the property in advance //note: this approach will only work if toValue != nil [CATransaction begin]; [CATransaction setDisableActions:YES]; [layer setValue:animation.toValue forKeyPath:animation.keyPath]; [CATransaction commit]; //apply animation to layer [layer addAnimation:animation forKey:nil]; } - (IBAction)changeColor { //create a new random color 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 *animation = [CABasicAnimation animation]; animation.keyPath = @"backgroundColor"; animation.toValue = (__bridge id)color.CGColor; //apply animation without snap-back [self applyBasicAnimation:animation toLayer:self.colorLayer]; }
6.虚拟属性
CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"transform.rotation"; animation.duration = 2.0; animation.byValue = @(M_PI * 2); [shipLayer addAnimation:animation forKey:nil];
用transform.rotation而不是transform作动画的好处以下:
咱们能够不经过关键帧一步旋转多于180度的动画。
能够用相对值而不是绝对值旋转(设置byValue而不是toValue)。
能够不用建立CATransform3D,而是使用一个简单的数值来指定角度。
不会和transform.position或者transform.scale冲突(一样是使用关键路径来作独立的动画属性)。
transform.rotation 属性有一个奇怪的问题是它其实并不存在。这是由于CATransform3D并非一个对象,它其实是一个结构体,也没有符合KVC相关属 性,transform.rotation其实是一个CALayer用于处理动画变换的虚拟属性。
7.动画组
//create group animation CAAnimationGroup *groupAnimation = [CAAnimationGroup animation]; groupAnimation.animations = @[animation1, animation2]; groupAnimation.duration = 4.0; //add the animation to the color layer [colorLayer addAnimation:groupAnimation forKey:nil];
8.过渡动画
CAAnimation有一个type和subtype来标识变换效果。type属性是一个NSString类型,能够被设置成以下类型:
kCATransitionFade
kCATransitionMoveIn
kCATransitionPush
kCATransitionReveal
经过subtype来控制它们的方向,提供了以下四种类型:
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom
隐式过渡
CATransision 能够对图层任何变化平滑过渡的事实使得它成为那些很差作动画的属性图层行为的理想候选。苹果固然意识到了这点,而且当设置了CALayer的 content属性的时候,CATransition的确是默认的行为。可是对于视图关联的图层,或者是其余隐式动画的行为,这个特性依然是被禁用的,但 是对于你本身建立的图层,这意味着对图层contents图片作的改动都会自动附上淡入淡出的动画。
对图层树的动画
CATransition并不做用于指定的图层属性,这就是说你能够在即便不能准确得知改变了什么的状况下对图层作动画,例如,在不知道 UITableView哪一行被添加或者删除的状况下,直接就能够平滑地刷新它,或者在不知道UIViewController内部的视图层级的状况下对 两个不一样的实例作过渡动画。
这些例子和咱们以前所讨论的状况彻底不一样,由于它们不涉及到图层的属性,并且是整个图层树的改变--咱们在这种动画的过程当中手动在层级关系中添加或者移除图层。
这里用到了一个小诡计,要确保CATransition添加到的图层在过渡动画发生时不会在树状结构中被移除,不然CATransition将会和图层一块儿被移除。通常来讲,你只须要将动画添加到被影响图层的superlayer。
- (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]; }
自定义动画
奇怪的是苹果经过UIView +transitionFromView:toView:duration:options:completion: 和+transitionWithView:duration:options:animations:方法提供了Core Animation的过渡特性。可是这里的可用的过渡选项和CATransition的type属性提供的常量彻底不一样。UIView过渡方法中 options参数能够由以下常量指定:
UIViewAnimationOptionTransitionFlipFromLeft
UIViewAnimationOptionTransitionFlipFromRight
UIViewAnimationOptionTransitionCurlUp
UIViewAnimationOptionTransitionCurlDown
UIViewAnimationOptionTransitionCrossDissolve
UIViewAnimationOptionTransitionFlipFromTop
UIViewAnimationOptionTransitionFlipFromBottom
过渡动画作基础的原则就是对原始的图层外观截图,而后添加一段动画,平滑过渡到图层改变以后那个截图的效果。若是咱们知道如何对图层截图,咱们就可使用属性动画来代替CATransition或者是UIKit的过渡方法来实现动画。
事实证实,对图层作截图仍是很简单的。CALayer有一个-renderInContext:方法,能够经过把它绘制到Core Graphics的上下文中捕获当前内容的图片,而后在另外的视图中显示出来。若是咱们把这个截屏视图置于原始视图之上,就能够遮住真实视图的全部变化, 因而从新建立了一个简单的过渡效果。
清单8.14演示了一个基本的实现。咱们对当前视图状态截图,而后在咱们改变原始视图的背景色的时候对截图快速转动而且淡出,图8.5展现了咱们自定义的过渡效果。
为 了让事情更简单,咱们用UIView -animateWithDuration:completion:方法来实现。虽然用CABasicAnimation能够达到一样的效果,可是那样的 话咱们就须要对图层的变换和不透明属性建立单独的动画,而后当动画结束的是哦户在CAAnimationDelegate中把coverView从屏幕中 移除。
清单8.14 用renderInContext:建立自定义过渡效果
- (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]; }]; }
CAMediaTiming协议
duration
repeatCount
repeatDuration
autoreverses
注意repeatCount和repeatDuration可能会相互冲突,因此你只要对其中一个指定非零值。对两个属性都设置非0值的行为没有被定义。
beginTime
指定了动画开始以前的的延迟时间。这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0(就是说动画会马上执行)。
speed
是一个时间的倍数,默认1.0,减小它会减慢图层/动画的时间,增长它会加快速度。若是2.0的速度,那么对于一个duration为1的动画,实际上在0.5秒的时候就已经完成了。
timeOffset
和beginTime相似,可是和增长beginTime致使的延迟动画不一样,增长timeOffset只是让动画快进到某一点,例如,对于一个持续1秒的动画来讲,设置timeOffset为0.5意味着动画将从一半的地方开始。
fillMode
fillMode是一个NSString类型,能够接受以下四种常量:
kCAFillModeForwards
kCAFillModeBackwards
kCAFillModeBoth
kCAFillModeRemoved
默认是kCAFillModeRemoved,当动画再也不播放的时候就显示图层模型指定的值剩下的三种类型向前,向后或者即向前又向后去填充动画状态,使得动画在开始前或者结束后仍然保持开始和结束那一刻的值。
这就对避免在动画结束的时候急速返回提供另外一种方案(见第八章)。可是记住了,当用它来解决这个问题的时候,须要把removeOnCompletion设置为NO,另外须要给动画添加一个非空的键,因而能够在不须要动画的时候把它从图层上移除。
暂停,倒回和快进(图层的speed)
设置动画的speed属性为0能够暂停动画,但在动画被添加到图层以后不太可能再修改它了,因此不能对正在进行的动画使用这个属性。给图层添加一个 CAAnimation其实是给动画对象作了一个不可改变的拷贝,因此对原始动画对象属性的改变对真实的动画并无做用。相反,直接用 -animationForKey:来检索图层正在进行的动画能够返回正确的动画对象,可是修改它的属性将会抛出异常。
若是移除图层正在进行的动画,图层将会急速返回动画以前的状态。但若是在动画移除以前拷贝呈现图层到模型图层,动画将会看起来暂停在那里。可是很差的地方在于以后就不能再恢复动画了。
一个简单的方法是能够利用CAMediaTiming来暂停图层自己。若是把图层的speed设置成0,它会暂停任何添加到图层上的动画。相似的,设置speed大于1.0将会快进,设置成一个负值将会倒回动画。
经过增长主窗口图层的speed,能够暂停整个应用程序的动画。这对UI自动化提供了好处,咱们能够加速全部的视图动画来进行自动化测试(注意对于在主窗口以外的视图并不会被影响,好比UIAlertview)。能够在app delegate设置以下进行验证:
self.window.layer.speed = 100;
你也能够经过这种方式来减速,但其实也能够在模拟器经过切换慢速动画来实现。
手动动画(图层的timeOffset)
timeOffset一个颇有用的功能在于你能够它可让你手动控制动画进程,经过设置speed为0,能够禁用动画的自动播放,而后来使用timeOffset来来回显示动画序列。这可使得运用手势来手动控制动画变得很简单。
@property (nonatomic, strong) CALayer *doorLayer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //add the door self.doorLayer = [CALayer layer]; self.doorLayer.frame = CGRectMake(0, 0, 128, 256); self.doorLayer.position = CGPointMake(150 - 64, 150); self.doorLayer.anchorPoint = CGPointMake(0, 0.5); self.doorLayer.contents = (__bridge id)[UIImage imageNamed:@"bjl_list_02"].CGImage; [self.view.layer addSublayer:self.doorLayer]; //apply perspective transform CATransform3D perspective = CATransform3DIdentity; perspective.m34 = -1.0 / 500.0; self.view.layer.sublayerTransform = perspective; //add pan gesture recognizer to handle swipes UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] init]; [pan addTarget:self action:@selector(pan:)]; [self.view addGestureRecognizer:pan]; //pause all layer animations self.doorLayer.speed = 0.0; //apply swinging animation (which won't play because layer is paused) CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"transform.rotation.y"; animation.toValue = @(-M_PI_2); animation.duration = 1.0; [self.doorLayer addAnimation:animation forKey:nil]; } - (void)pan:(UIPanGestureRecognizer *)pan { //get horizontal component of pan gesture CGFloat x = [pan translationInView:self.view].x; //convert from points to animation duration //using a reasonable scale factor x /= 200.0f; //update timeOffset and clamp result CFTimeInterval timeOffset = self.doorLayer.timeOffset; timeOffset = MIN(0.999, MAX(0.0, timeOffset - x)); self.doorLayer.timeOffset = timeOffset; //reset pan gesture [pan setTranslation:CGPointZero inView:self.view]; }