Core Animation 基于一个假说, 屏幕上任何东西均可以(或者可能)作动画. 动画并不须要再Core Animation中手动打开, 相反的须要明确的关闭, 不然将一直存在.网络
当改变CALayer的一个可作动画的属性, 改变并不会马上在屏幕上体现出来, 它会从从前的值平滑的过渡到新的值. 这一切都是默认的行为, 不须要咱们作任何操做.dom
这就是隐式动画. 咱们并不须要指定动画类型, 仅仅改变一个属性, 而后Core Animation会决定如何而且什么时候去作动画. 实际上, 当改变一个属性, 动画执行时间取决于当前事务的设置, 动画类型取决于图层的行为. 事务是Core Animation用来包含一系列属性动画集合的机制, 任何用指定事务去改变能够作动画的图层的属性都不会马上发生改变, 而是当事务提交的时候, 开始一个动画过分到新的值.函数
事务是经过CATransaction类来管理的, 这个类管理了一叠不能访问的事务, 没有属性和实例方法, 而且不能alloc, init建立它, 可是能够用+begin, +commit分别来入栈和出栈. 任何能够作动画的图层属性都会被添加到栈顶的事务, 能够经过+setAnimationDuration:
设置当前事务的动画时间, 或者 +animationDuration
来获取值.oop
Core Animation在每一个run loop周期自动开始一次新的事务(run loop是iOS负责收集用户输入, 处理定时器或者网络时事件而且从新绘制屏幕的东西), 即便不显式的用[CATransaction begin]开始一次事务, 任何一个run loop中属性的改变都会被集中起来, 而后作一次0.25秒的动画.测试
#pragma mark - 测试事务控制CALayer动画 - (void)changeButtonClick:(UIButton *)sender { // 开始一个新的事物 [CATransaction begin]; // 设置事务的 动画执行时间 [CATransaction setAnimationDuration:1.f]; // 禁用动画 [CATransaction setDisableActions:NO]; // 设置事务执行完成后的代码块 [CATransaction setCompletionBlock:^{ NSLog(@"Animation over"); self.colorLayer.affineTransform = CGAffineTransformRotate(self.colorLayer.affineTransform, M_PI_4); }]; //randomize the layer background color CGFloat red = arc4random() / (CGFloat)INT_MAX; CGFloat green = arc4random() / (CGFloat)INT_MAX; CGFloat blue = arc4random() / (CGFloat)INT_MAX; self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; sender.layer.backgroundColor = self.colorLayer.backgroundColor; // 提交事务和动画 [CATransaction commit]; }
UIView 经过几个类方法完成的动画效果, 实际上就是都是因为设置了 CATransaction的缘由.iOS4中, 苹果味UIView的添加了基于block的动画写发, 事实上也是基于CATransaction的动画.动画
基于UIView的block的动画容许你在动画结束的时候提供一个完成的动做。CATranscation接口提供的+setCompletionBlock:方法也有一样的功能。如上代码所示.3d
如上第一节, 直接对CALayer对象修改动画属性都会引发动画效果, 可是测试发现, 直接对UIView关联的图层作动画而不是独立的图层, 这时候动画效果消失了. 隐式动画被UIView的关联图层给禁用了.代理
咱们把改变属性时CALayer自动应用的动画称做行为, 当CALayer的属性被修改时候, 它会调用-actionForKey:
方法, 传递属性的名称. 剩下的操做在CALayer头文件中有详细的说明, 有如下几个步骤:code
actionForLayer:forKey:
方法, 若是有, 直接调用并返回结果.actions
字典.actions字典
没有包含对应的属性, 那么图层接着在它的style字典中搜索属性名.style
里面也找不到对应的行为, 那么图层将会直接调用定义了每一个属性标准行为的-defaultActionForKey:
方法.因此通过一轮的搜索, -actionForKey:
要么返回空(这时候将不会有动画发生), 要么是CAAction协议对应的对象, 最后CALayer拿着这个结果去对先前和当前的值作动画.orm
UIKit如何禁用隐式动画: 每一个UIView对它关联的图层都扮演了一个委托, 而且提供了-actionForLayer:forKey:
的实现方法. 当不在一个动画块的实现中, UIView对全部图层都返回nil, 可是在block范围以内, 就返回一个非空值. 因而咱们能够预言, 当属性在动画块以外发生改变, UIView直接经过返回nil来禁用隐式动画. 但若是在动画块范围以内, 根据动画具体类型返回相应的属性.
CATransaction有个方法叫作: +setDisableActions:
, 能够用来对全部属性打开或者关闭隐式动画. 在 [CATransaction begin]
以后添加[CATransaction setDisableActions:YES];
代码, 就能够阻止动画的发生.
总结一下:
-actionForLayer:forKey:
方法, 或者直接建立一个显式动画.-actionForLayer:forKey:
委托方法, 或者提供一个actions字典来控制隐式动画.对于单独的图层, 设置actions
字典要比实现代理更加简单一点. 行为一般是一个被Core Animation隐式调用的显式动画对象, 好比使用CATransition
, 由于它能够相应CAAction
协议, 而且能够做为图层行为. 经过此对象, 咱们能够自定义隐式动画的动画样式. demo:
#pragma mark - 测试过渡动画 - (void)changeButtonClick1:(UIButton *)sender { CATransition *transition = [CATransition animation]; transition.type = kCATransitionPush; transition.subtype = kCATransitionFromLeft; self.colorLayer.actions = @{@"backgroundColor": transition}; //randomize the layer background color CGFloat red = arc4random() / (CGFloat)INT_MAX; CGFloat green = arc4random() / (CGFloat)INT_MAX; CGFloat blue = arc4random() / (CGFloat)INT_MAX; self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; sender.layer.backgroundColor = self.colorLayer.backgroundColor; }
CALayer的属性行为很不寻常, 由于改变一个图层的属性而没有当即生效, 而是经过一段时间渐变动新, 这是怎么作到的呢? 这一小节作简单的笔记.
当改变图层属性时候, 属性值是当即被更新的(当读取它的数据时候, 会发现它的值在设置那一刻就已经生效了), 可是屏幕并无当即发生变化. 这是由于你设置的属性并无直接调整图层的外观, 相反, 它只是定义了图层动画结束以后将要改变的外观.
当设置CALayer的属性, 其实是在定义当当前事务结果时候图层将如何显示的模型. Core Animation扮演一个控制器的角色, 而且负责根据图层行为和事务设置去不断更新视图的这些属性在屏幕上的状态.
在iOS中, 屏幕每秒钟别重绘60次. 若是动画时长比六十分之一秒要长, CoreAnimation就须要在设置新值和新值生效之间, 对屏幕图层进行从新组织, 就意味着, CALayer除了真实值, 以外, 必须谁知道当前显示在屏幕上的属性值的记录.
每一个图层属性的显示值都被存储在一个叫作呈现图层的独立图层中, 能够经过-presentationLayer
方法来访问. 这个呈现图层其实是模型图层的复制, 可是它的属性值, 表明了在任何指定时刻当前外观的效果. 换句话说, 你能够经过呈现图层的值来获取当前屏幕上真正显示出来的值.
除了图层树, 还有呈现树, 呈现树是经过图层树的呈现图层造成的. 呈现树仅仅是图层首次被提交(第一次在屏幕上显示)的时候建立, 因此在那以前调用 -presentationLayer
将会返回nil.
通常状况下, 咱们不须要直接访问呈现图层, 咱们通常都经过和模型图层的交互让CoreAnimation更显显示. 但有时候仍是须要用到呈现图层:
-hitTest:
方法来判断图层是否被触摸, 这时候使用呈现图层而不是模型图层调用-hitTest:
会更有意义, 由于呈现图层表明用户当前看到的位置, 而不是动画结束以后的位置.小demo:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { CGPoint point = [[touches anyObject] locationInView:self.view]; if ([self.moveLayer.presentationLayer hitTest:point]) { CGFloat red = arc4random() / (CGFloat)INT_MAX; CGFloat green = arc4random() / (CGFloat)INT_MAX; CGFloat blue = arc4random() / (CGFloat)INT_MAX; [CATransaction begin]; [CATransaction setAnimationDuration:1]; self.moveLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1].CGColor; [CATransaction commit]; }else{ [CATransaction begin]; [CATransaction setAnimationDuration:4]; self.moveLayer.position = point; [CATransaction commit]; } }