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

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使用详解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342