Core Animation: CALayer 及其隐式动画

要做出具有相当高的用户体验的 APP,适当的使用动画必不可少。iOS 中的动画有很多种,从其他的博文中,你可能会找到很多诸如基础动画关键帧动画 , 转场动画等等。这些动画其实都是基于 CALayer 的变换和隐式动画的组合。这篇文章将详细介绍 CALayer 和它的隐式动画。

一、CALayer

CALayer 是一个很基础的类,它被包含在 Core Animation 中,Core Animation 不能根据它的字面意思来理解,它的真实身份来源于 Layer Kit ,动画只是它的一部分,并且是很小的一部分。Core Animation 是一个复合引擎,它的存在目的是帮助系统以尽可能快的速度将屏幕上不同的可视内容组合起来,存放于图层树的体系中。我们在 iOS 上看到的一切东西都由 Core Animation 来呈现,包括动画。CALayer 作为 Core Animation 中基本的图层,它提供了可视化功能,我们使用的 UIView 本身不可见,只是因为其内部承载了一个 CALayer 才具有可见功能。而 UIView 则提供了响应事件的能力,本质上,UIView 负责事件链的传递和响应,CALayer 则负责视图的呈现。

CALayer 之所以能够显示内容,因为它包含一个 contents 属性,这个属性的类型虽然为 id类型,但是,如果我们填入了非图片类型的数据之后,会使得 CALayer 显示空白。CALayer的contents接受来自其他的(通常是 Core Graphics)绘制内容组成的图片,加入其中,这个图片被称之为寄宿图。

每一个 UIView 都有一个 CALayer 相联系, UIView 默认是对应 CALayer 的代理,我们通过 UIView 的 layer 属性就能够访问到它。大体上,UIView 已经很完善了,不仅能够看见,还能够进行用户交互,对于更加底层的 CALayer,我们是在没有过多接触的必要。但不代表我们不会使用到它 —— CALayer 相比 UIView 具有如下优势:

  • 设置视图的圆角、阴影、边框
  • 3D变换
  • 不规则的形状
  • 透明遮罩

以上的功能,在日常的开发中或多或少的会使用到。而这些通过 CALayer 可很容易的做到,UIView 并没有这些功能。

CALayer 绘制

同时在绘制方面,UIView 也是基于 CALayer 来进行的。 通常我们会在代码中使用

- (void) DrawRect;

为 UIView 进行重绘。当我们实现了这个方法的时候,它会在 UIView 呈现的时候被调用。或者我们也可以主动使用-(void)setNeedsDisplay方法来间接调用, 另外,在 UIView 的 bounds 被修改的时候,这个重绘方法也会被调用。

根本原因是,UIView 被当作 CALayer 的代理,调用 - (void) DrawRect;将自动运行 CALayer 的绘制功能代码,也就是说, UIView 帮我们将如何调用 CALayer 绘制的动作给做了。

CALayerDelegate的代理方法中,有以下两个方法:

-(void)displayLayer:(CAlayer)layer;

-(void)drawLayer:(CALayer)layer inContet:(CgContentRef)ctx;

-(void)displayLayer:(CAlayer)layer;可以给layer接受一个来自于 UIView 的绘制内容,我们也可以不实现这个方法,那么代理会继续调用下面的-(void)drawLayer:(CALayer)layer inContet:(CgContentRef)ctx;方法,这个方法中将直接绘制内容到 layer 上。当然这些工作不需要我们自己来做,UIView 会替我们完成,我们做的仅仅是实现UIView的- (void) DrawRect;就可以了。

我们可以自己设置 layer 的代理,然后进行代理方法的实现,这样也能得到和 UIView 自动完成代理一样的效果。

坐标系

之前说了,UIView 的一切可视化都来自于它的 layer,因此包括 UIView 的frame,bounds,center内容都来自CALayer, 这三个属性在CALayer中分别对应CALayer的属性:frame,bounds,positionframe 其实是一个虚拟属性,它并不实际存在,而是通过 boundsposition计算得到的。他们的主要功能是描述视图的位置属性。UIVIew 中这三个属性任何一个进行改变,实际上都是改变了对应layer的三个对应属性。

除此之外,CALayer 还有一个很关键的属性anchorPoint-- 锚点。它是一个单位量点,取值范围在(0-1)之间, 这个点在默认状态下是(0.5,0.5),我们改变它会将整个视图的位置偏移:

CALayer锚点

CALayer的 zPosition

一个很简单的例子是,当我们在同一个位置覆盖两个 UIView 的时候,后添加的 View 将会覆盖 底下的 View,这个顺序将决定事件响应链的传递之机制。


绿色在下

如果我们设置底下 view 对应的 CALayer 的 zPosition 在原来的基础上加1,那么我们将看到:

设置zPosition

zPosition表示的是图层的层级属性数值,每个图层的该默认值都是0.如果有一个图层比0大,哪怕是0.001,这个图层就会被推到最前面。
但是要注意的是,尽管 zPosition 改变了图层的显示顺序,却对每个视图之间的事件传递没有影响。上图中,绿色矩形原先被覆盖的部分会收到红色矩形的影响,尽管我们从视觉上看到它在上面。所以,事件的响应和图层的显示顺序并没有必然的联系。

CALayer 的事件处理

Core Animation 没有给 CALayer 添加事件处理机制,我们不能使用它来进行交互,如果要使用交互的话,我们可以使用两个方法检测用户的交互事件:

- (Bool) containPoint;
- (CALayer*)hitTest;

- (Bool) containPoint;将检测一个 layer 是不是包含了某个点,它返回一个 Bool 值。而 - (CALayer*)hitTest;则检测当前点处在某个 layer 之上,并将 layer 返回。
我们可以通过这两个方法,结合 屏幕Touch回调函数来检测用户的交互事件。

更多 CALayer 的使用知识,在我的另一篇博文中有介绍:
http://www.cnblogs.com/FBiOSBlog/p/6900534.html

二、CALayer 的隐式动画

Core Animation中,假设 CALayer 每个属性的改动都是有动画的(如果有可能)。对于单独的 CALayer,每一个属性的的改动,如果存在对应的属性动画,则会被实现并展现出来。因为 CALayer 这种特质的动画不需要进行任何动画代码的修饰就能够体现动画效果,所以这些动画被统称为隐式动画。但是CALayer 的隐式动画在 UIView 中不会被触发, 更确切地说是被 UIVIew 禁用了。

为了证明 UIView 禁用了隐式动画的事实,我写了一段代码:

    _layer = [CALayer layer];
    _layer.frame = CGRectMake(100, 100, 100, 100);
    _layer.backgroundColor = [UIColor blueColor].CGColor;
    
    _view = [[UIView alloc]initWithFrame:CGRectMake(220, 100, 100, 100)];
    _view.backgroundColor = [UIColor blueColor];

    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
    
    btn.frame = CGRectMake([UIScreen mainScreen].bounds.size.width/2.0 - 50, 250, 100, 50);
    [btn setTitle:@"点击" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(changeColor) forControlEvents:UIControlEventTouchUpInside];
    
    [self.view addSubview:_view];
    [self.view addSubview:btn];
    
    [self.view.layer addSublayer:_layer];
    
}

- (void)changeColor{
    UIColor *color = [UIColor colorWithRed:random()%255/256.0 green:random()%255/256.0 blue:random()%255/256.0 alpha:1];
  
   // 设置 隐式动画的时长
    [CATransaction setAnimationDuration:1]; 
    _layer.backgroundColor = color.CGColor;
    _view.backgroundColor = color;
}

效果:

同样的都是改变视图的颜色,CALayer 会自动执行动画,(其中为了区别,我将隐式动画的事件拉长了,隐式动画的默认时长为0.25s),但是 UIView 却没有动画,尽管两者都没有任何其他的有关动画代码的修饰,这就是我们所说的 CALayer 的隐式动画。和隐式动画相对的还有显示动画,显示动画的使用其实是在隐式动画的基础上加上动画的事务 CATransaction ,CATransaction 是一个很特殊的类,它不提供任何初始化的方法,只暴露了部分 API,它的使用目的是让我们手动设置隐式动画为显式动画,并能够改动动画的效果。比如上面的代码:[CATransaction setAnimationDuration:1];就是改变了动画的时长。我们如果要使用 CATransaction 构成显式动画可以这样做:

    //给CALayer 开放显示动画
    [CATransaction begin];
    [CATransaction setAnimationDuration:1.5];
    _layer.backgroundColor = color.CGColor;
    [CATransaction commit];

按照我们之前说的内容,UIView 是通过 CALayer 的显示特性来呈现视图的,既然 CALayer 具有隐式动画效果,UIView 应该也能够获取这个特性才是,原因是它将隐式动画效果禁用了。 UIView 中,默认禁用隐式动画,但是我们可以通过 UIView 的一些 API 来开放这些动画。将上面的修改 UIView 颜色的那部分代码修改:

    //给 UIVIew 添加动画
    [UIView beginAnimations:@"animation" context:nil];
    [UIView setAnimationDuration:1.5];
    _view.backgroundColor = color;
    [UIView commitAnimations];

再看看效果:

由此可见,UIView 并非不能执行动画,而是需要在特定的环境中进行。iOS 中,我们除了使用 [UIView beginAnimations:@"animation" context:nil];[UIView commitAnimations];之外,还有一系列的其他手段开启动画。像 UIView 的Block动画 animationWithDuration:animations:将动画的内容放在 block 中,以避免开发者动画开始到提交的闭合操作失误。但不管是哪一种动画,其内部都会调用CALayer 的事务 CATransaction 来进行相关的设置。

动画在APP的合理使用能够提升用户的体验,但是也会有部分性能的消耗,最主要的是,我们需要在合适的场景使用动画才更有意义,所以将隐式动画的开启开关交给开发者更为可靠。

那 UIView 禁用隐式动画的原理是什么呢?

在 CALayer 中,所有的动画被统称为: action —— 行为。CALayer 每次执行隐式动画之前,会执行一个函数:

  - actionForkey()  

这个函数内部其实做了以下事情:

  • 首先查看layer本身是否有委托,并且检查这个委托是否实现了- actionForLayer: forKey代理方法,如果有这个方法,直接调用并返回结果。 否则进入到下一步。
  • layer 接下来将会检查包含属性名对应行为映射的字典 actions。 actions 字典以属性名为key,以属性的行为为 value 存储。这是将决定一个属性是否有动画的过程之一。如果 actions 没有属性名,则到下一步。
  • 除了 actions 字典,layer 还有一个 style 字典,这个字典中包含了一些固定的类型对应的动画,这是对不太好在 actions 定义的属性行为的补充。
  • 最后,如果在上述中都找不到行为,那么图层调用每个属性对应的标准行为。也就是隐式动画的本身。

我们在使用 CATransaction 事务对动画做出修改的时候,实际上是在 actions 字典中添加行为,它具有比标准行为更高的优先级,一次每次改动总能生效。

我们知道 UIView 默认是对应的 CALayer 的代理,禁用动画的方式就是实现- actionForLayer: forKey代理方法,并在方法中直接返回 nil,这样动画就生生被切掉了。

当我们使用了 UIView 的动画 API 的时候,这段期间 - actionForLayer: forKey 方法将返回我们实现的动画内容。这就是为什么我们调用这些 API 能够显示动画的原因了。那么如何给 UIView 永久开启隐式动画我们就有了思路了。

给 UIView 永久开启隐式动画。

其实开启隐式动画的方式我们已经做过了,就是在 UIView 的动画 API 中书写动画有关的代码。如果想要像 CALayer 一样,不需要其他的 API 也能给予 UIView 动画的话,我们或许可以创建一个类,继承自 UIView,并重写它的- actionForLayer: forKey 方法。当然这么做,可能有点麻烦,你可能更倾向于直接使用一个显示的动画吧。不过我们试试总归是可以的,我创建了一个MyAnimationView 类,它继承自 UIView ,然后实现它的- actionForLayer: forKey 方法:

#import "MyAnimationView.h"

@implementation MyAnimationView

- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;{
    return [layer animationForKey:event];
}

@end

然后就像平常使用View一样使用它:

- (void)viewDidLoad {
    [super viewDidLoad];
    _view = [[MyAnimationView alloc]initWithFrame:CGRectMake(220, 100, 100, 100)];
    _view.backgroundColor = [UIColor blueColor];
    [self.view addSubview:_view];
  btn.frame = CGRectMake([UIScreen mainScreen].bounds.size.width/2.0 - 50, 250, 100, 50);
    [btn setTitle:@"点击" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(changeColor) forControlEvents:UIControlEventTouchUpInside];
    
    [self.view addSubview:btn];
}

- (void)changeColor{
    UIColor *color = [UIColor colorWithRed:random()%255/256.0 green:random()%255/256.0 blue:random()%255/256.0 alpha:1];
   
   // 这里我并没有添加任何和动画有关的代码  直接修改颜色
    _view.backgroundColor = color;
}
实现了隐式动画

隐式动画就介绍到这里,CALayer 还有很多其他有用的特性,有兴趣的同学可以多多挖掘。

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

推荐阅读更多精彩内容