浅谈CALayer的隐式动画及事务
一、前言
本文是为了后续直播App送礼大动画实战演练做铺垫, 浅谈CALayer的隐式动画及事务.
全文主要涉及以下问题:
- CALayer图层的定义
- 何为隐式动画?
- 所有CALayer都有隐私动画吗? UIView的backing layer呢?
- 哪些对象遵循CAAction协议
- 动画事务是什么?
二、 CALayer
CALayer图层, 是数据模型, 数据对象. 对于iOS平台, 一个UIView视图在展示之前, 系统都会为其创建一个支持图层(backing layer), 其中就储存了View外貌样式的表现内容, 比如图层通过contents属性来管理bitmap位图, 从而充当位图的容器.
Backing layer的delegate(CALayerDelegate)就是该图层所属的view对象.
三、 隐式动画
我们平常都肯定使用过显式动画, 但我们比较少见到的隐式动画(implicit animation)又是什么.
从Core Animation Guide官方指南中, 并没有找到隐式动画的明确定义, 仅提及了以下相关内容:
- 直接更改图层CALayer的属性就会触发隐式动画, 但是修改UIView对象支持图层(backing layer)的动画属性是不会发生隐式动画的, 因为UIView默认禁止了backing layer的隐式动画, 所以对backing layer属性的修改在UIView上的反应是直接变化, 没有平滑过度的动画效果
- 隐式动画会使用当前动画事务的参数默认值来执行动画
- 隐式动画会直接更改了layer模型中的值, 而显式动画不会(在动画执行完后, 会根据layer模型的属性进行"还原", 所以我们在添加动画后需要手动修改layer的属性来确保动画完成时, 图层能够"还原"到动画结束时的位置! 只有presentationLayer中的值会随着动画执行不断改变.)
- 隐式动画执行过程中无法直接被移除, 而显式动画可以通过实例方法removeAnimationForKey:或removeAllAnimations来直接移除
- Core Animation通过 遵循CAAction协议的对象 来实现隐式动画
此处来一发示例, 帮助我们理解一下上方第1点:
/*
现有UIView对象 viewA, 以及一个CALayer对象 layerB, 我们把 layerB 添加到
viewA 的 layer(这个就叫backing layer) 上.
*/
// 对backing layer进行属性修改, 不会发生隐式动画, 而是由之前的颜色直接变成红色
viewA.layer.backgroundColor = [UIColor redColor].CGColor;
// layerB作为viewA.layer的子图层, 会发生隐式动画, layerB的颜色会由原先的颜色平滑过渡到蓝色
layerB.backgroundColor = [UIColor blueColor].CGColor;
至此, 想必我们已经清楚CALayer何时会发生隐式动画了, 但后半句中说UIView的backing layer不会有隐式动画又是何解?
这里需要关联第5点中提到的遵循CAAction协议的对象
实际上, CAAnimation及其派生类, 如CABasicAnimation、CASpringAnimation, 以及CATransition都遵循并实现了 CAAction 协议中的方法. 当CALayer的动画属性改变时就会去查找匹配的CAAniamtion对象来执行动画. 其查找流程用代码形式表示如下:
/*
1. 若返回遵循 CAAction 协议对象, 则用其执行动画(runActionForKey:object:arguments:)
2. 若该方法返回nil, 不执行隐式动画, 直接更新属性
*/
- (id<CAAction>)searchActionForLayer:(CALayer *)layer forKey:(NSString *)key {
id<CAAction> action = nil;
// 先问代理该key对应的Action, 如果返回不为nil, 则已找到; 如果返回nil, 则继续执行查找逻辑; 如果返回NSNull对象则停止剩余查找逻辑, 最终返回NSNull
if ([layer.delegate respondsToSelector:@selector(actionForLayer:forKey:)]) {
id<CAAction> action = [layer.delegate actionForLayer:layer forKey:key];
if ([NSNull null] == action) {
return nil;
}
else if (action) {
return action;
}
}
if (!action) {
action = [self actionForKey:key in:layer.actions];
}
if (!action) {
for (NSDictionary<NSString*, id<CAAction>> *actions in layer.style) {
if ((action = [self actionForKey:key in:actions])) {
break;
}
}
}
if (!action) {
action = [[layer class] defaultActionForKey:key];
}
return action;
}
- (id<CAAction>)actionForKey:(NSString *)key in:(NSDictionary<NSString*, id<CAAction>> *)actions {
for (NSString *actionKey in actions.allKeys) {
if ([actionKey isEqualToString:key]) {
return actions[actionKey];
}
}
return nil;
}
UIView作为backing layer的delegate(as CALayerDelegate), 实现了-actionForLayer:forKey方法; 当不处于动画block范围内时, 该方法返回nil; 否则, 返回对应的动画对象. 关联上方函数注释若该方法返回nil, 不执行隐式动画, 直接更新属性
, 这就是为什么UIView的backing layer不会有隐式动画.
四、 动画事务
动画事务, 也类似于数据库事务, 用于组合某个逻辑里边的一系列操作. Core Animation会自动为我们某个图层的单个或多个显式动画、隐式动画创建隐式事务. 当然, 我们也可以通过 CATransaction 的类方法, 来显式创建事务.
// 显式事务
[CATransaction begin];
[CATransaction setValue:@(0.3) forKey:kCATransactionAnimationDuration];
layer.opacity = 0.0;
[CATransaction commit];
// UIView提供用来做动画的类方法等价于上方显式创建动画, 因为这些类方法内部就调用了CATransaction的+begin, +commit方法
[UIView animateWithDuration:0.3 animations:^{
layer.opacity = 0.0;
}];
当runloop开始一次新的循环时, Core Animation就会开启一个事务, 在本次循环中的所有动画操作, 包括我们显示创建的事务都会被嵌套其中, 直至本次runloop循环结束之时, 再一块提交进行动画. 比如下方代码:
// layerA、layerB、layerC均为寄宿/单独图层(hosted layer/standalone layer)
// --- runloop 新的循环开始 ---
[CATransaction begin]; // Core Animation 开始的新事务
// -- 本次循环中, 我们涉及的动画操作 start --
// 修改单独图层属性, 将以最外层CATransaction的动画参数发起隐式动画, 动画时间为当前所在事务的动画时间, 即默认的0.25秒
layerA.backgroundColor = [UIColor redColor].CGColor;
// 创建显式事务, 嵌套事务
[CATransaction begin];
[CATransaction setValue:@(1) forKey:kCATransactionAnimationDuration];
// 当前事务中的隐式动画, 执行时间为1秒
layerB.position = CGPoint(110, 119);
[CATransaction commit];
// 实际也相当于嵌套事务
[UIView animateWithDuration:0.5 animations:^{
layerC.opacity = 0.0;
}];
// -- 本次循环中, 我们涉及的动画操作 end --
[CATransaction commit]; // Core Animation 提交所有事务
// --- runloop 循环结束 ---
利用事务, 禁止动画发生:
[CATransaction begin];
// 设置 kCATransactionDisableActions 为 false
[CATransaction setValue:@(false)
forKey:kCATransactionDisableActions];
// UIView Animation显示动画失效
[UIView animateWithDuration:0.33
animations:^{
// 单独的图层layerA
layerA.backgroundColor = [UIColor redColor].CGColor;
}];
// 单独的图层layerB, 隐式动画失效
layerB.opacity = 0.0;
[CATransaction commit];
五、推荐资料
本文提及内容仅为Core Animation的冰山一角, 如果想深入了解iOS动画, 可以尝试阅读一下下方资料, 定会有一番收获!
Core Animation Guide
iOS Core Animation
CATransaction In Depth
网友评论