美文网首页
第七篇:layer的隐式动画

第七篇:layer的隐式动画

作者: 意一ineyee | 来源:发表于2018-01-19 16:49 被阅读56次

    Core Animation之CALayer一集中,我们介绍了Core Animation除了动画之外所有能做到的事情,但是我们也知道动画是Core Animation一个非常重要的特性,而动画又可以分为隐式动画显式动画,本篇先介绍隐式动画。


    目录

    隐式动画的几个关键词

    一、CALayer可动画属性列表

    二、隐式动画

    1、什么是隐式动画?
    2、如何使用隐式动画?

    三、事务(CATransaction)

    1、什么是事务?
    2、如何使用事务?

    四、图层行为(CAAnimation或其子类)

    1、什么是图层行为?
    2、隐式动画的实现机制
    3、如何使用图层行为

    五、什么情况下使用隐式动画


    隐式动画的几个关键词:

    • 隐式动画可以当作是个基础动画,但是有个但是
    • 事务(CATransaction)
    • 图层行为(CAAnimation或其子类)

    一、CALayer可动画属性列表

    最普通的属性 说明
    backgroundColor --
    frame --
    bounds --
    position --
    zPosition 改Z轴上位置,一般使用transform的平移来代替
    anchorPoint --
    显隐性属性 说明
    opacity --
    hidden --
    仿射变换和3D变换属性 说明
    affineTransform layer的仿射变换
    transform layer的3D变换
    切圆角、设置边框和设置阴影属性 说明
    cornerRadius --
    masksToBounds --
    borderColor --
    borderWidth --
    shadowColor --
    shadowOffset --
    shadowOpacity --
    shadowRadius --
    layer寄宿图的一堆属性 说明
    contentsXXX --

    二、隐式动画

    1、什么是隐式动画?

    Core Animation基于一个假设,说是屏幕上显示的任何东西都可以做动画,而我们又知道显示在屏幕上的东西本质都是CALayer,所以Core Animation就为每一个显示在屏幕上的东西都默认添加了一个动画,这个动画就是隐式动画。

    那么具体来说,隐式动画就是指当我们改变layer的某个可动画属性时,它这个属性不会立马跳变到新值,而是会从旧值平滑地过渡到新值,而且这个过程是自动完成的,我们不需要做任何额外的操作。相反的,在某些场景下我们反而需要明确地关闭隐式动画,否则它会一直存在。

    因为隐式动画只能作用于layer的属性,且无法设置关键帧,因此隐式动画也可以看做是layer显式动画基础动画之外的一种基础动画它可以直接作用于layer的可动画属性,但是不能直接作用于UIView关联的那个layer的可动画属性,因为UIKit框架把UIView关联的layer的隐式动画给禁用了,但是我们可以通过UIView block基础动画的形式来改变UIView关联的layer的可动画属性,这样的使用方式UIKit框架是没有把UIView关联的layer的隐式动画给禁用的。

    2、如何使用隐式动画?

    从隐式动画的概念我们可以看出,使用隐式动画其实很简单,我们不需要做任何操作,只需要直接改变CALayer某个可做动画的属性就可以了。

    下面举一个简单的例子看一下:和上一篇一样,让一条弹幕从右往左飘过去,那就得使用一些CATextLayer来显式文本了。

    (清单2.1):

    //
    //  ViewController.m
    //  CoreAnimation
    //
    //  Created by 意一yiyi on 2017/11/13.
    //  Copyright © 2017年 意一yiyi. All rights reserved.
    //
    
    #import "ViewController.h"
    
    #define kScreenWidth [UIScreen mainScreen].bounds.size.width
    #define kScreenHeight [UIScreen mainScreen].bounds.size.height
    
    @interface ViewController ()
    
    @property (strong, nonatomic) CATextLayer *layer;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.layer = [[CATextLayer alloc] init];
        self.layer.backgroundColor = [UIColor magentaColor].CGColor;
        self.layer.frame = CGRectMake(kScreenWidth, 100, kScreenWidth, 30);
        
        // 防止字体模糊
        self.layer.contentsScale = [UIScreen mainScreen].scale;
        
        // 文本内容
        self.layer.string = @"嘿Siri,你在哪儿?";
        
        // 字体颜色
        self.layer.foregroundColor = [UIColor whiteColor].CGColor;
        
        // 字体大小
        UIFont *font = [UIFont systemFontOfSize:17];
        CFStringRef fontName = (__bridge CFStringRef)font.fontName;
        CGFontRef fontRef = CGFontCreateWithFontName(fontName);
        self.layer.font = fontRef;
        self.layer.fontSize = font.pointSize;
        CGFontRelease(fontRef);
        
        // 对齐方式
        self.layer.alignmentMode = kCAAlignmentLeft;
        
        [self.view.layer addSublayer:self.layer];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        self.layer.affineTransform = CGAffineTransformMakeTranslation(-kScreenWidth, 0);
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    
    1.gif

    运行,发现我们在-touchesBegan里确实是直接改变了layer的属性值,没做其它任何操作,layer就平滑地过渡到结束位置,而不是跳变,很方便,不必像UIView block动画那样还得写一个方法,但是也得注意到为了创建一行文本,我们使用CATextLayer所做的工作要比label大太多了。

    问题来了:这个动画时间太短了,而且我们也想像上一篇UIView block基础动画那样,在动画结束后改变文本的内容,怎么做呢?这就要用到事务了。

    三、事务(CATransaction)

    1、什么是事务?

    事务是一个用来管理隐式动画的类,它没有属性也不用初始化,只是提供了一些方法来帮助我们管理隐式动画。例如下面5个常用的方法:

    +begin:开启一个事务
    +commit:提交一个事务
    +setDisableActions:是否启用隐式动画
    +setAnimationDuration:设置动画时长(隐式动画默认0.25s)
    +setCompletionBlock:设置动画完成之后要做的事

    2、如何使用事务?

    事务的使用也非常简单,只需要三步:开启事务隐式动画的一些设置及改变属性的操作提交事务

    我们把(清单2.1)中的弹幕时间延长,并且在动画结束之后改变文本的内容:

    (清单2.2)

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        // 开始事务
        [CATransaction begin];
        
        // 隐式动画的一些设置
        [CATransaction setAnimationDuration:5];
        [CATransaction setCompletionBlock:^{
            
            self.layer.string = @"你在哪儿,我就在哪儿!";
        }];
        
        // 改变属性的操作(属性的改变要写在开始事务和提交事务中间)
        self.layer.affineTransform = CGAffineTransformMakeTranslation(-kScreenWidth, 0);
        
        // 提交事务
        [CATransaction commit];
    }
    
    1.gif

    运行,我们发现通过事务,我们完成对隐式动画的一些设置的管理。

    问题来了:我们可以看到这里弹幕的移动效果是easyInEastOut,如果我们也想像上一篇UIView block基础动画那样让弹幕匀速移动过去,怎么做呢?这就要用到图层行为了。

    四、图层行为(CAAnimation或其子类)

    1、什么是图层行为?

    我们把改变CALayer的可动画属性时,Core Animation所做的动画称作该图层的行为。由此我们看出,图层行为的本质其实就是一个显示动画,它是CAAnimation或其子类

    2、隐式动画的实现机制

    上面我们演示了隐式动画作用于我们创建的layer,那么在介绍如何使用图层行为之前,我们先做一个实验看看隐式动画能不能作用于UIView所关联的那个layer的可动画属性

    (清单2.3):

    //
    //  ViewController.m
    //  CoreAnimation
    //
    //  Created by 意一yiyi on 2017/11/13.
    //  Copyright © 2017年 意一yiyi. All rights reserved.
    //
    
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @property (strong, nonatomic) UIView *customView;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.customView = [[UIView alloc] init];
        self.customView.frame = CGRectMake(100, 100, 100, 100);
        self.customView.layer.backgroundColor = [UIColor redColor].CGColor;
        [self.view addSubview:self.customView];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        // 开始事务
        [CATransaction begin];
        
        // 隐式动画设置
        [CATransaction setAnimationDuration:3];
        
        // 改变属性(属性的改变要写在开始事务和提交事务中间)
        CGFloat red = arc4random()%256 / 255.0;
        CGFloat green = arc4random()%256 / 255.0;
        CGFloat blue = arc4random()%256 / 255.0;
        self.customView.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1].CGColor;
        
        // 提交事务
        [CATransaction commit];
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    
    1.gif

    运行,我们发现图层的颜色瞬间就跳变到了新值,并没有渐变的过渡效果。为什么啊?因为UIKit框架把UIView关联的layer的隐式动画给禁用了。那么UIKit框架是如何把UIView关联的layer的隐式动画给禁用的呢?为了回答这个问题,我们需要知道隐式动画的实现机制

    • 当我们改变CALayer的可动画属性时,CALayer就会调用它的-actionForKey:方法,把要改变的属性作为参数传进去,然后去查找这个属性所对应的图层行为来执行动画,那么这个根据属性查找行为的过程主要分下面四个步骤:
      • 首先layer会检测它是否有代理,并且检测代理是否实现了CALayerDelegate协议里的-actionForLayer:forKey:代理方法,如果有代理并且实现了该代理方法,就会直接返回该代理方法里设置的图层行为。
      • 如果在代理方法里没找到行为,那么layer就会检测它一个叫做actions的属性,这个属性是一个保存着layer可动画属性及属性对应行为映射关系的字典。如果在actions里检测到属性,就会返回属性所对应的行为。
      • 如果在actions字典里没找到行为,那么layer就会检测它一个叫做style的属性,style字典和actions字典差不多,检测方式也差不多,先不用管它俩的区别, 主要用actions。
      • 如果在style字典里没找到行为,那么layer就会调用-defaultActionForKey:方法返回该属性对应的默认行为来执行动画。

    所以经过一轮搜索之后:-actionForKey:方法要么返回空,layer不做动画;要么返回一个图层行为,layer拿这这个结果去做动画。

    以上我们就知道了隐式动画的实现机制,我们就可以知道UIKit框架是如何把UIView关联的layer的隐式动画给禁用的了就是每个view其实都是它关联的layer的代理,并且实现了-actionForLayer:forKey:代理方法,当不在UIView的block动画内修改layer的属性时,代理方法就返回nil不做隐式动画,当在UIView的block动画内修改layer的属性时,代理方法就返回非空值正常使用隐式动画。我们可以举个例子简单验证一下:

    (清单2.4):

    //
    //  ViewController.m
    //  CoreAnimation
    //
    //  Created by 意一yiyi on 2017/11/13.
    //  Copyright © 2017年 意一yiyi. All rights reserved.
    //
    
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @property (strong, nonatomic) UIView *customView;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.customView = [[UIView alloc] init];
        self.customView.frame = CGRectMake(100, 100, 100, 100);
        self.customView.layer.backgroundColor = [UIColor redColor].CGColor;
        [self.view addSubview:self.customView];
        
        NSLog(@"UIView的block动画外===%@", [self.customView.layer actionForKey:@"backgroundColor"]);
        
        [UIView animateWithDuration:3 animations:^{
            
            NSLog(@"UIView的block动画内===%@", [self.customView.layer actionForKey:@"backgroundColor"]);
        }];
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    

    运行,控制台输出:

    (清单2.5)

    2018-01-11 13:05:26.672314+0800 CoreAnimation[8128:2267431] UIView的block动画外===(null)
    2018-01-11 13:05:26.678047+0800 CoreAnimation[8128:2267431] UIView的block动画内===<CABasicAnimation: 0x11be95ae0>
    

    因此,我们只需要把(清单2.3)的代码替换为下面的代码就可以在改变view关联的layer的背景色时带有隐式动画了。

    (清单2.6)

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
        [UIView animateWithDuration:3 animations:^{
            
            // 改变属性(属性的改变要写在开始事务和提交事务中间)
            CGFloat red = arc4random()%256 / 255.0;
            CGFloat green = arc4random()%256 / 255.0;
            CGFloat blue = arc4random()%256 / 255.0;
            self.customView.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1].CGColor;
        }];
    }
    
    1.gif
    3、如何使用图层行为

    上面我们知道了图层行为的本质其实就是一个显示动画,它是CAAnimation或其子类,也知道了隐式动画的机制就是根据可动画属性去查找这个属性对应的图层行为来做动画,并且有四步,所以我们就可以任意创建一个显示动画替换掉系统默认的某个属性对应的图层行为,从而达到自定义隐式动画的效果,来让隐式动画更好玩。我们可以在隐式动画实现机制的过程中任一节点来完成图层行为的替换,比如:

    • 我们可以实现-actionForLayer:forKey:代理方法返回一个自定义的行为;
    • 我们也可以自定义layer的actions属性来自定义图层行为;
    • 我们也可以自定义layer的style属性来自定义图层行为。

    当然了,最常用的还是第二种方式,因为代码写的少啊。即改变layer的actions属性来自定义图层行为,从而达到修改隐式动画的效果

    下面我们就通过替换图层行为来让弹幕匀速飘过

    (清单2.7)

    //
    //  ViewController.m
    //  CoreAnimation
    //
    //  Created by 意一yiyi on 2017/11/13.
    //  Copyright © 2017年 意一yiyi. All rights reserved.
    //
    
    #import "ViewController.h"
    
    #define kScreenWidth [UIScreen mainScreen].bounds.size.width
    #define kScreenHeight [UIScreen mainScreen].bounds.size.height
    
    @interface ViewController ()
    
    @property (strong, nonatomic) CATextLayer *layer;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.layer = [[CATextLayer alloc] init];
        self.layer.backgroundColor = [UIColor magentaColor].CGColor;
        self.layer.frame = CGRectMake(kScreenWidth, 100, kScreenWidth, 30);
    
        // 防止字体模糊
        self.layer.contentsScale = [UIScreen mainScreen].scale;
    
        // 文本内容
        self.layer.string = @"嘿Siri,你在哪儿?";
    
        // 字体颜色
        self.layer.foregroundColor = [UIColor whiteColor].CGColor;
    
        // 字体大小
        UIFont *font = [UIFont systemFontOfSize:17];
        CFStringRef fontName = (__bridge CFStringRef)font.fontName;
        CGFontRef fontRef = CGFontCreateWithFontName(fontName);
        self.layer.font = fontRef;
        self.layer.fontSize = font.pointSize;
        CGFontRelease(fontRef);
    
        // 对齐方式
        self.layer.alignmentMode = kCAAlignmentLeft;
        
        // 创建一个过渡动画:匀速
        CATransition *transition = [CATransition animation];
        transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        // 替换图层行为
        self.layer.actions = @{@"affineTransform.position": transition};
    
        [self.view.layer addSublayer:self.layer];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
        // 开始事务
        [CATransaction begin];
    
        // 隐式动画的一些设置
        [CATransaction setAnimationDuration:5];
        [CATransaction setCompletionBlock:^{
    
            self.layer.string = @"你在哪儿,我就在哪儿!";
        }];
    
        // 改变属性的操作(属性的改变要写在开始事务和提交事务中间)
        self.layer.affineTransform = CGAffineTransformMakeTranslation(-kScreenWidth, 0);
    
        // 提交事务
        [CATransaction commit];
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    
    1.gif

    五、什么情况下使用隐式动画

    通过上面弹幕的例子,我们使用隐式动画一一对应UIView block基础动画、带动画完成回调的UIView block基础动画、可以自定义过渡动画效果的UIView block基础动画实现了和上一篇类似的效果,但是隐式动画是无法实现Spring动画的,而且我们也发现同样一个简单的弹幕效果,使用UIView block基础动画要简单得多,而使用隐式动画则显得麻烦很多,所以大多数情况下如果要做基础动画是优先选择UIView block基础动画的,那隐式动画到底什么时候才会用到啊?

    • 第一、我们可以看到CALayer的可动画属性要比UIView的可动画属性多多了,除了最简单背景色、显隐性、平移缩放旋转大家都可以做之外,CALayer还多了关于切圆角、设置边框
      设置阴影和3D变换的可动画属性,因此如果有这方面的属性动画需求,可以使用隐式动画
      当然不是让我们直接创建CALayer来使用,还是使用UIView,只不过是让隐式动画作用于view关联的layer+view关联layer属性的变化写在UIView block动画内来实现。

    如登录按钮的形变及切圆角效果:

    (清单2.8)

    //
    //  ViewController.m
    //  CoreAnimation
    //
    //  Created by 意一yiyi on 2017/11/13.
    //  Copyright © 2017年 意一yiyi. All rights reserved.
    //
    
    #import "ViewController.h"
    
    #define kScreenWidth [UIScreen mainScreen].bounds.size.width
    #define kScreenHeight [UIScreen mainScreen].bounds.size.height
    
    @interface ViewController ()
    
    @property (strong, nonatomic) UIButton *button;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.button = [[UIButton alloc] init];
        self.button.backgroundColor = [UIColor magentaColor];
        self.button.frame = CGRectMake(100, 100, 100, 100);
        [self.view addSubview:self.button];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        [UIView animateWithDuration:3 animations:^{
            
            self.button.bounds = CGRectMake(0, 0, 200, 100);
            
            // 使用layer的隐式动画来切圆角
            self.button.layer.cornerRadius = 50;
        }];
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    
    1.gif
    • 第二、我们可以看到隐式动画的图层行为是可以自定义的,而且图层行为可以是任意的显示动画,所以效果肯定要比UIView block基础动画自带的可设置的过渡效果要多得多,所以如果在做基础动画的时候如果发现想要更好玩的效果,就可以考虑使用隐式动画。

    比如切换一个view的背景色,我们要一个从左往右的推入推出效果,使用UIView block基础动画是没法实现的,而使用隐式动画替换图层行为就可以方便地实现,如下:

    //
    //  ViewController.m
    //  CoreAnimation
    //
    //  Created by 意一yiyi on 2017/11/13.
    //  Copyright © 2017年 意一yiyi. All rights reserved.
    //
    
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @property (strong, nonatomic) CALayer *customLayer;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.customLayer = [[CALayer alloc] init];
        self.customLayer.frame = CGRectMake(100, 100, 100, 100);
        self.customLayer.backgroundColor = [UIColor redColor].CGColor;
        
        // 创建一个过渡动画
        CATransition *transition = [CATransition animation];
        transition.type = kCATransitionPush;
        transition.subtype = kCATransitionFromLeft;
        // 替换图层行为
        self.customLayer.actions = @{@"backgroundColor": transition};
        [self.view.layer addSublayer:self.customLayer];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        // 开始事务
        [CATransaction begin];
        
        // 隐式动画设置
        [CATransaction setAnimationDuration:3];
        
        // 改变属性(属性的改变要写在开始事务和提交事务中间)
        CGFloat red = arc4random()%256 / 255.0;
        CGFloat green = arc4random()%256 / 255.0;
        CGFloat blue = arc4random()%256 / 255.0;
        self.customLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1].CGColor;
        
        // 提交事务
        [CATransaction commit];
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    
    1.gif

    相关文章

      网友评论

          本文标题:第七篇:layer的隐式动画

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