iOSiOS学习笔记iOS Developer

你所不知道的CALayer隐式动画及事务

2017-06-05  本文已影响434人  ShawnFoo

浅谈CALayer的隐式动画及事务

一、前言

本文是为了后续直播App送礼大动画实战演练做铺垫, 浅谈CALayer的隐式动画及事务.

全文主要涉及以下问题:

二、 CALayer

CALayer图层, 是数据模型, 数据对象. 对于iOS平台, 一个UIView视图在展示之前, 系统都会为其创建一个支持图层(backing layer), 其中就储存了View外貌样式的表现内容, 比如图层通过contents属性来管理bitmap位图, 从而充当位图的容器.

Backing layer的delegate(CALayerDelegate)就是该图层所属的view对象.

三、 隐式动画

我们平常都肯定使用过显式动画, 但我们比较少见到的隐式动画(implicit animation)又是什么.

从Core Animation Guide官方指南中, 并没有找到隐式动画的明确定义, 仅提及了以下相关内容:

  1. 直接更改图层CALayer的属性就会触发隐式动画, 但是修改UIView对象支持图层(backing layer)的动画属性是不会发生隐式动画的, 因为UIView默认禁止了backing layer的隐式动画, 所以对backing layer属性的修改在UIView上的反应是直接变化, 没有平滑过度的动画效果
  2. 隐式动画会使用当前动画事务的参数默认值来执行动画
  3. 隐式动画会直接更改了layer模型中的值, 而显式动画不会(在动画执行完后, 会根据layer模型的属性进行"还原", 所以我们在添加动画后需要手动修改layer的属性来确保动画完成时, 图层能够"还原"到动画结束时的位置! 只有presentationLayer中的值会随着动画执行不断改变.)
  4. 隐式动画执行过程中无法直接被移除, 而显式动画可以通过实例方法removeAnimationForKey:或removeAllAnimations来直接移除
  5. 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

上一篇 下一篇

猜你喜欢

热点阅读