前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS动画-CALayer隐式动画原理与特性

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

作者头像
梧雨北辰
发布2019-04-25 09:50:56
4.6K0
发布2019-04-25 09:50:56
举报
文章被收录于专栏:梧雨北辰的开发录

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

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

一、何为隐式动画?

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

代码语言:javascript
复制
@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去创建它,它的常用操作如下:

代码语言:javascript
复制
//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秒的动画。 现在,我们就通过事务来设置动画做一个验证,代码如下:

代码语言:javascript
复制
- (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:方法传递属性名称,我们可以找到这个方法的具体说明如下:

代码语言:javascript
复制
/* 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范围就返回了非空值,下面通过一段代码来验证:

代码语言:javascript
复制
@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也为我们提供了具体的方法,可以用来对所有属性打开或者关闭隐式动画,方法如下:

代码语言:javascript
复制
+ (void)setDisableActions:(BOOL)flag;

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

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

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

四、自定义图层行为

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

代码语言:javascript
复制
@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字典实现了自定义的图层行为;

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019.04.17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、何为隐式动画?
  • 二、隐式动画的原理
    • 1.事务
      • 2.图层行为
      • 三、关闭和开启隐式动画
      • 四、自定义图层行为
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档