美文网首页iOSiOS学习笔记iOS Developer
你所不知道的CALayer隐式动画及事务

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

作者: ShawnFoo | 来源:发表于2017-06-05 18:10 被阅读434次

    浅谈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官方指南中, 并没有找到隐式动画的明确定义, 仅提及了以下相关内容:

    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

    相关文章

      网友评论

        本文标题:你所不知道的CALayer隐式动画及事务

        本文链接:https://www.haomeiwen.com/subject/lxcwfxtx.html