一:树状结构
CAAnimation并不是一个单纯的实现动画的框架,它原本叫Layer Kit,它管理着树状结构的图层数据,并且快速组合这些图层,构成了一切可视化的基础.
在构建可视化,也就是视图的时候,iOS中使用UIKit中的UIView,但是mac OS中使用AppKit中的NSView,但是他们的layer都是CALayer;这是因为鼠标键盘和触摸屏差距很大,为了方便跨平台并且解耦等等原因而进行了功能的拆分,于是就有了CoreAnimation和UIKit/AppKit.
CALayer有三个树状结构:
model layer tree
模型树,这个数据结构存储图层属性的目标值,在没有动画的时候,目标值就是当前显示的效果,super -> sub1 + sub2 +..+sub3->sub3.1+...这就是父层与子层的树状结构.presentation tree
显示树,这个数据结构存储图层属性的当前值,在动画的过程中,它是不断变化的,是只读的.CALayer对象有一个presentationLayer属性,可以读取当前的动画状态,因此presentation tree和model layer tree的对象是一一对应的.-
render tree
渲染树,用于执行动画,这是图层的私有对象,Apple没有对它进行解释.
二.事务
创建一个CALayer,添加一个按钮来改变它的背景颜色
_layer = [CALayer layer];
_layer.frame = CGRectMake(100, 100, 200, 200);
_layer.backgroundColor = UIColor.blackColor.CGColor;
[self.view.layer addSublayer:_layer];
//按钮事件
_layer.backgroundColor = [UIColor colorWithRed:arc4random_uniform(255)/255.0 green:arc4random_uniform(255)/255.0 blue:arc4random_uniform(255)/255.0 alpha:1.0].CGColor;
会发现CALayer改变backgroundColor的时候,是一个动画效果(0.25秒)
CoreAnimation会自动产生动画,当修改CALayer的可动画属性(Animatable)时,会默认产生一个0.25秒的动画来过渡到目标值,而不是立即改变成目标值.
上面的例子,backgroundColor改成position一样生效,而且看起来更明显.
这便是CoreAnimation的隐式动画,之所以叫隐式是因为我们只是修改了一个属性的值,没有指定任何动画,更没有定义动画如何执行,但是系统自动产生了动画.
事务CATransaction便是CoreAnimation处理属性动画的机制
CATransaction没有构造方法,没有实例方法,只有一些类方法.通过begin和commit来入栈和出栈.
在一次runloop中,任何Animatable属性发生变化,都会向栈顶加入新的事务, setAnimationDuration和animationDuration可以设置和获取当前事务的执行时长.
CGPoint po = _layer.position;
[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
[CATransaction setCompletionBlock:^{
}];
_layer.backgroundColor = [UIColor colorWithRed:arc4random_uniform(255)/255.0 green:arc4random_uniform(255)/255.0 blue:arc4random_uniform(255)/255.0 alpha:1.0].CGColor;
_layer.position = CGPointMake(po.x + arc4random_uniform(100), po.y + arc4random_uniform(100));
[CATransaction commit];
这段代码同时修改了position和backgroundColor,并且设置了执行时长
这种同时改变一堆属性的动画是不是非常眼熟,是的,+animateWithDuration:animations:completion:实际就是CATransaction的封装.
另外UIView还有+beginAnimations:context:和+commitAnimations方法对应CATransaction的begin和commit.
把开头的那段代码的CALayer换成UIView,会发现动画没了,这是因为UIKit把隐式动画给禁了.即便使用第二个例子,用上[CATransaction begin]和commit,UIView也不会产生动画.为什么呢.
当CALayer的属性被修改时,图层首先检测它是否有代理并且是否实现CALayerDelegate的actionForLayer:forKey方法。如果有,直接调用并返回结果。
如果没有,图层接着检查包含属性名称对应行为映射的actions字典,如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。
最后,如果在style里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:方法.
所以一轮完整的搜索结束之后,-actionForKey:要么返回空,这时没有动画,要么是CAAction协议对应的对象,最后CALayer拿这个结果去对先前和当前的值做动画。
每个UIView对它关联的layer都实现了-actionForLayer:forKey方法,如果添加了事务,那么返回非空,如果没有,则返回空.
因此,+animateWithDuration:animations:completion:中的代码是可以执行动画的,否则没有动画.这个方法的block会给到每个
CATransaction还有 +setDisableActions:方法可以直接开启或者关闭所有的属性动画
_layer = [CALayer layer];
_layer.frame = CGRectMake(100, 100, 200, 200);
_layer.backgroundColor = UIColor.blackColor.CGColor;
[self.view.layer addSublayer:_layer];
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
_layer.actions = @{@"backgroundColor": transition};
//按钮事件
CATransaction begin];
[CATransaction setAnimationDuration:1.0];
_layer.backgroundColor = [UIColor colorWithRed:arc4random_uniform(255)/255.0 green:arc4random_uniform(255)/255.0 blue:arc4random_uniform(255)/255.0 alpha:1.0].CGColor;
[CATransaction commit];
还是第一个例子,给_layer添加actions字典,每当颜色发生改变时,颜色本身的变化是瞬间完成的,但是会有一个1秒的渐变动画产生.
当执行事务的时候,也就是改变了backgroundColor,这时候layer搜索了actions字典,发现"backgroundColor"对应有值transition,于是就执行这个transition动画.
前面说到layer的模型树和显示树,模型树只会保存属性的最终值,而显示树会把初始值到最终值期间的过程值全部算一遍,通过layer的presentationLayer属性访问,并且它也是一个CALayer,在layer第一次显示出来的时候被创建,在属性动画的过程中,它的对于属性的值一直在发生变化.
例如一个position动画,layer在运动中,如果想要知道有没有点击到layer,此时需要presentationLayer的-hitTest来判断而不是layer自己的-hitTest.可以在touchbegin中使用[self.layer.presentationLayer hitTest:point]来判定.
三:显式动画
前面说的是CoreAnimation的隐式动画,而CAAnimation就是显式动画了,UIView禁止了隐式动画,但是CAAnimation可以为UIView的layer添加显式动画,显式动画并没有修改属性的值,只是执行动画而已,因此还需要主动修改属性.
但是如果给没有绑定UIView的CALayer对象添加CAAnimation时,由于这个layer带有隐式动画,所以一但修改属性的值,再加上CAAnimation就会出现两次动画
CGPoint po = _layer.position;
CGPoint po1 = CGPointMake(po.x + arc4random_uniform(100), po.y + arc4random_uniform(100));
CABasicAnimation *a1 = [CABasicAnimation animationWithKeyPath:@"position"];
a1.fromValue = [NSValue valueWithCGPoint:po];
a1.toValue = [NSValue valueWithCGPoint:po1];
a1.duration = 1.0;
[_layer addAnimation:a1 forKey:nil];
_layer.position = po1;
}
这段代码会执行两次动画,而且时间不一样.
解决方法,要么在动画执行之前赋值(需要先保存值作为fromValue),要么在动画完全结束之后赋值(需要在CAAnimationDelegate的-animationDidStop:finished:中实现,或者dispatch_after也勉强可行),再或者是禁用CATransaction的隐式动画.