美文网首页
iOS动画-CALayer隐式动画原理与特性

iOS动画-CALayer隐式动画原理与特性

作者: 梧雨北辰 | 来源:发表于2019-04-17 09:00 被阅读0次

    Core Animation的一个非常显著的特性是就是实现动画,而且它支持隐式动画和显式动画两种形式,本篇我们主要从隐式动画说起;

    本篇主要内容:
    1.何为隐式动画
    2.隐式动画原理-事务与图层行为
    3.隐式动画的关闭与显示
    4.隐式动画自定义图层行为

    一、何为隐式动画?

    Core Animation是基于这样的一个假设:屏幕上的任何东西都可以(或者可能)做动画,它并不需要手动打开,反而是需要我们明确的关闭,否则动画会一直存在。所谓隐式动画,其实是指我们可以在不设定任何动画类型的情况下,仅仅改变CALayer的一个可做动画的属性,就能实现动画效果。
    这听起来似乎不太真实,我们可以通过下面的代码来验证,使用随机色修改了CALayer的背景色:

    @interface TestLayerAnimationVC ()
    @property (nonatomic,strong) CALayer *colorLayer;
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        _colorLayer = [[CALayer alloc] init];
        _colorLayer.frame = CGRectMake(30, 30, kDeviceWidth - 60, 60);
        [self.view.layer addSublayer:_colorLayer];
    }
    
    - (IBAction)changeColor:(UIButton *)sender{
        CGFloat red = arc4random() % 255 / 255.0;
        CGFloat green = arc4random() % 255 / 255.0;
        CGFloat blue = arc4random() % 255 / 255.0;
        UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
        
        _colorLayer.backgroundColor = randomColor.CGColor;
    }
    

    效果图如下:


    测试隐式动画.gif

    经过测试,我们会发现每次设置的颜色并不是立刻在屏幕上跳变出来,相反,它是从先前的值平滑过渡到新的值,这一切都是默认行为,你不需要做额外的操作,这就是隐式动画。

    二、隐式动画的原理

    当我们改变一个CALayer属性时,Core Animation是如何判断动画类型和持续时间呢?实际上动画执行的时间取决于当前事务的设置,动画类型则取决于图层行为。

    1.事务

    事务,其实是Core Animation用来包含一系列属性动画集合的机制,通过指定事务来改变图层的可动画属性,这些变化都不是立刻发生变化的,而是在事务被提交的时候才启动一个动画过渡到新值。任何可以做动画的图层属性都会被添加到栈顶的事务。

    事务是通过CATransaction类来做管理,它没有属性或者实例方法,而且也不能通过alloc和init去创建它,它的常用操作如下:

    //1.动画属性的入栈
    + (void)begin;
    
    //2.动画属性出栈
    + (void)commit;
    
    //3.设置当前事务的动画时间
    + (void)setAnimationDuration:(CFTimeInterval)dur;
    
    //4.获取当前事务的动画时间
    + (CFTimeInterval)animationDuration;
    
    //5.在动画结束时提供一个完成的动作
    + (void)setCompletionBlock:(nullable void (^)(void))block;
    

    现在再来考虑隐式动画,其实是Core Animation在每个RunLoop周期中会自动开始一次新的事务,即使你不显式的使用[CATranscation begin]开始一次事务,任何在一次RunLoop运行时循环中属性的改变都会被集中起来,执行默认0.25秒的动画。
    现在,我们就通过事务来设置动画做一个验证,代码如下:

    - (IBAction)changeColor:(UIButton *)sender{
        [CATransaction begin];  //入栈
        //1.设置动画执行时间
        [CATransaction setAnimationDuration:3];
        //2.设置动画执行完毕后的操作:颜色渐变之后再旋转90度
        [CATransaction setCompletionBlock:^{
            CGAffineTransform transform = self.colorLayer.affineTransform;
            transform  = CGAffineTransformRotate(transform, M_PI_2);
            self.colorLayer.affineTransform = transform;
        }];
        
        CGFloat red = arc4random() % 255 / 255.0;
        CGFloat green = arc4random() % 255 / 255.0;
        CGFloat blue = arc4random() % 255 / 255.0;
        UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
        _colorLayer.backgroundColor = randomColor.CGColor;
        [CATransaction commit];  //出栈
    }
    

    效果图如下:


    测试隐式动画事务.gif

    可以看到,CALayer颜色的渐变动画已经变为了3秒,而旋转动画由于是默认事务变化,仍然以0.25秒快速执行。

    2.图层行为

    我们上述的实验对象是一个独立图层,如果直接对UIView或者CALayer关联的图层layer改变动画属性,这样是没有隐式动画效果的,这说明虽然Core Animation对所有的CALayer动画属性设置了隐式动画,但UIView把它关联的图层的这个特性给关闭了。
    为了更好的理解中一点,我们需要知道隐式动画是如何实现的:
    我们把改变属性时CALayer自动执行的动画称作行为,当CALayer的属性被修改时,它会调用-actionForKey:方法传递属性名称,我们可以找到这个方法的具体说明如下:

    /* Returns the action object associated with the event named by the
     * string 'event'. The default implementation searches for an action
     * object in the following places:
     *
     * 1. if defined, call the delegate method -actionForLayer:forKey:
     * 2. look in the layer's `actions' dictionary
     * 3. look in any `actions' dictionaries in the `style' hierarchy
     * 4. call +defaultActionForKey: on the layer's class
     *
     * If any of these steps results in a non-nil action object, the
     * following steps are ignored. If the final result is an instance of
     * NSNull, it is converted to `nil'. */
    
    - (nullable id<CAAction>)actionForKey:(NSString *)event;
    

    翻译过来大概就是说:

    1. 图层会首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法;如果有,就直接调用并返回结果。
    2. 如果没有委托或者委托没有实现-actionForLayer:forKey方法,图层将会检查包含属性名称对应行为映射的actions字典
    3. 如果actions字典没有包含对应的属性,图层接着在它的style字典里搜索属性名.
    4. 最后,如果在style也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的+defaultActionForKey:方法

    从流程上分析来看,经过一次完整的搜索动画之后,-actionForKey:要么返回空(这种情况不会有动画发生),要么返回遵循CAAction协议的对象(CALayer拿这个结果去对先前和当前的值做动画)。现在我们再来考虑UIKit是如何禁用隐式动画的:
    每个UIView对它关联的图层都遵循了CALayerDelegate协议,并且实现了-actionForLayer:forKey方法。当不在一个动画块中修改动画属性时,UIView对所有图层行为都返回了nil,但是在动画Block范围就返回了非空值,下面通过一段代码来验证:

    @interface TestLayerAnimationVC ()
    
    @property (nonatomic,weak)IBOutlet UIView *layerView;
    
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
       //测试图层行为:UIKit默认关闭了自身关联图层的隐式动画
        NSLog(@"OutSide:%@",[self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
       
        [UIView beginAnimations:nil context:nil];
        NSLog(@"InSide:%@",[self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
        [UIView commitAnimations];
    }
    
    //打印:
    OutSide:<null>
    InSide:<CABasicAnimation: 0x600001703100>
    

    由此得出结论:当属性在动画块之外发生变化,UIView直接通过返回nil来禁用隐式动画。但是如果在动画块范围内,UIView则会根据动画具体类型返回响应的属性,

    三、关闭和开启隐式动画

    当然,返回nil并不是禁用隐式动画的唯一方法,CATransaction也为我们提供了具体的方法,可以用来对所有属性打开或者关闭隐式动画,方法如下:

    + (void)setDisableActions:(BOOL)flag;
    

    UIView关联的图层禁用了隐式动画,那么对这种图层做动画的方法有有了以下几种方式:

    1. 使用UIView的动画函数(而不是依赖CATransaction)
    2. 继承UIView,并覆盖-actionforLayer:forkey:方法
    3. 直接创建显式动画

    其实,对于单独存在的图层,我们也可以通过实现图层的-actionforLayer:forkey:方法,或者提供一个actions字典来控制隐式动画

    四、自定义图层行为

    通过对事务和图层行为的了解,我们可以这样思考,图层行为其实是被Core Animation隐式调用的显式动画对象。我们可以发现改变隐式动画的这种图层行为有两种方式:
    1.给layer设置自定义的actions字典
    2.实现委托代理,返回遵循CAAction协议的动画对象
    现在,我们尝试使用第一种方法来自定义图层行为,这里用到的是一个推进过渡的动画(也是遵循了CAAction的动画类),具体的代码如下:

    @interface TestLayerAnimationVC ()
    @property (nonatomic,strong) CALayer *colorLayer;
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        _colorLayer = [[CALayer alloc] init];
        _colorLayer.frame = CGRectMake(30, 30, kDeviceWidth - 60, 60);
        _colorLayer.backgroundColor = [UIColor orangeColor].CGColor;
        //自定义动画对象
        CATransition *transition = [CATransition animation];
        transition.type = kCATransitionPush;
        transition.subtype = kCATransitionFromLeft;
        _colorLayer.actions = @{@"backgroundColor":transition};
        [self.view.layer addSublayer:_colorLayer];
    }
    
    - (IBAction)changeColor:(UIButton *)sender{
        CGFloat red = arc4random() % 255 / 255.0;
        CGFloat green = arc4random() % 255 / 255.0;
        CGFloat blue = arc4random() % 255 / 255.0;
        UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
        _colorLayer.backgroundColor = randomColor.CGColor;
    }
    

    效果图如下:


    测试隐式动画-自定义图层行为.gif

    经测试,我们会看到colorLayer将会以从左到右推进过渡的形式改变色值;我们通过给layer设置自定义的actions字典实现了自定义的图层行为;

    ---End---
    相关文章:
    iOS动画-CALayer寄宿图与绘制原理
    iOS动画-CALayer布局属性详解
    iOS动画-CALayer隐式动画原理与特性
    iOS动画-CAAnimation使用详解

    相关文章

      网友评论

          本文标题:iOS动画-CALayer隐式动画原理与特性

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