iOS 初探究CALayer图层

认识CALayer

CALayer类在概念上和UIView类似,同样也是一些被层级关系树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置。 它们有一些方法和属性用来做动画和变换。
和 UIView 最大的不同是 CALayer 不 处理用户的交互。并不清楚具体的响应链(iOS通过视图层级关系用来传送触摸事件的机 制),于是它并不能够响应事件,即使它提供了一些方法来判断是否一个触点在图层的范围之内
CALayer不会出现任何UIKit的东西

UIView与CALayer的关系

图层的树状结构

巨妖有图层,洋葱也有图层,你有吗?我们都有图层 -- 史莱克

Core Animation其实是一个令人误解的命名。你可能认为它只是用来做动画的, 但实际上它是从一个叫做Layer Kit这么一个不怎么和动画有关的名字演变而来,所 以做动画这只是Core Animation特性的冰山一角。

Core Animation是一个复合引擎,它的职责就是尽可能快地组合屏幕上不同的可 视内容,这个内容是被分解成独立的图层,存储在一个叫做图层树的体系之中。于 是这个树形成了UIKit以及在iOS应用程序当中你所能在屏幕上看见的一切的基础。

平行的层级关系

每一个 UIview 都有一个 CALayer 实例的图层属性,也就是所谓的backing layer,视图的职责就是创建并管理这个图层,以确保当子视图在层级关系中添加或者被移除的时候,他们关联的图层也同样对应在层级关系树当中有相同的操作

在这里插入图片描述
实际上这些背后关联的图层才是真正用来在屏幕上显示和做动画, UIView 是对它的一个封装,提供了一些iOS类似于处理触摸的具体功能,以及Core Animation底层方法的高级接口。

为什么UIView 和 CALayer要同时存在呢?为什 么不用一个简单的层级来处理所有事情呢?

原因在于要做职责分离,这样也能避免 很多重复代码。在iOS和Mac OS两个平台上,事件和用户交互有很多地方的不同, 基于多点触控的用户界面和基于鼠标键盘有着本质的区别,这就是为什么iOS有 UIKit和 UIView ,但是Mac OS有AppKit和 NSView 的原因。他们功能上很相似,但是在实现上有着显著的区别
实际上,这里并不是两个层级关系,而是四个,每一个都扮演不同的角色,除了 视图层级和图层树之外,还存在呈现树和渲染树

UIView 没有暴露出来的CALayer的功能:

阴影,圆角,带颜色的边框
3D变换
非矩形范围
透明遮罩
多级非线性动画

属性

@property(nullable, strong) id contents;

你可以给 contents 属性赋任何值,你的app 仍然能够编译通过。但是,在实践中,如果你给 contents 赋的不是CGImage, 那么你得到的图层将是空白的。所以必须用_bridge转换一下

UIImage *image = [UIImage imageNamed:@"bigWhite"];
centerView.layer.contents = (__bridge id _Nullable)(image.CGImage);//因为CGImageRef并不是一个真正的 Cocoa对象,而是一个Core Foundation类型。

contentGravity

相当于view.contentMode

centerView.layer.contentsGravity = kCAGravityCenter;

maskToBounds

默认情况下,UIView仍然会绘制超过边界的内容或是子视 图,在CALayer下也是这样的。
UIView有一个叫做 clipsToBounds 的属性可以用来决定是否显示超出边界的内 容,CALayer对应的属性叫做 masksToBounds

contentsRect

默认的 contentsRect 是{0, 0, 1, 1},如果设置的比默认值小,就会自动裁剪,如果给 contentsRect 设置一个负数的原点或是大于{1, 1}的尺寸也是可以的。 这种情况下,最外面的像素会被拉伸以填充剩下的区域。
这个技术可以用来拼接图片等,大多用在游戏开发中

contentsCenter

看名字你可能 会以为它可能跟图片的位置有关,不过这名字着实误导了 你。 contentsCenter 其实是一个CGRect,它定义了一个固定的边框和一个在图 层上可拉伸的区域。 改变 contentsCenter 的值并不会影响到寄宿图的显示,除非这个图层的大小改变了,你才看得到效果。
默认情况下, contentsCenter 是{0, 0, 1, 1},这意味着如果大小 (由 conttensGravity 决定)改变了,那么寄宿图将会均匀地拉伸开。但是如果我们增加原点的值并减小尺寸。我们会在图片的周围创造一个边框
在这里插入图片描述

自定义绘图

通常操作为在继承于UIView的类中并实现 -drawRect: 方法 来自定义绘制
如果UIView检测到 drawRect: 方法被调用了,它就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以contentsScale的值。
如果你不需要寄宿图,那就不要创建这个方法了,这会造成CPU资源和内存的浪 费,这也是为什么苹果建议:如果没有自定义绘制的任务就不要在子类中写一个空 的-drawRect:方法。

当视图在屏幕上出现的时候-drawRect: 方法就会被自动调用。

-drawRect: 方法里面的代码利用Core Graphics去绘制一个寄宿图,然后内容就会 被缓存起来直到它需要被更新(通常是因为开发者调用了 -setNeedsDisplay 方 法,尽管影响到表现效果的属性值被更改时,一些视图类型会被自动重绘, 如 bounds 属性)。

虽然 -drawRect: 方法是一个UIView方法,事实上都是底层的CALayer安排了重绘工作和保存了因此产生的图片。

CALayer有一个可选的 delegate 属性,实现了 CALayerDelegate 协议,当 CALayer需要一个内容特定的信息时,就会从协议中请求。CALayerDelegate是一 个非正式协议,其实就是说没有CALayerDelegate @protocol可以让你在类里面引 用啦。你只需要调用你想调用的方法,CALayer会帮你做剩下的。( delegate 属 性被声明为id类型,所有的代理方法都是可选的)。
当需要被重绘时,CALayer会请求它的代理给他一个寄宿图来显示。它通过调用下面这个方法做到的:
(void)displayLayer:(CALayerCALayer *)layer;
趁着这个机会,如果代理想直接设置 contents 属性的话,它就可以这么做, 不然没有别的方法可以调用了。如果代理不实现 -displayLayer: 方法, CALayer就会转而尝试调用下面这个方法:

  • (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;

所以在自定义View里只要实现 -drawRect:方法,就可以绘制图了。

图层几何学

布局

UIView有三个比较重要的布局属 frame , bounds 和 center ,
CALayer对应地叫 做 frame ,bounds和position。
为了能清楚区分,图层用了“position”,视图用了“center”,但是他们都代表同样的值

视图的属性frame,bounds和center仅仅是存取方法,当操纵视图 frame ,实际上是在改变位于视图下方 CALayer 的 frame ,不能够独立于图层之外改变视图的frame

对于视图或者图层来说, frame 并不是一个非常清晰的属性,它其实是一个虚 拟属性,是根据 bounds , position 和 transform 计算而来,所以当其中任何一个值发生改变,frame都会变化。相反,改变frame的值同样会影响到他们当中的值

记住当对图层做变换的时候,比如旋转或者缩放, frame 实际上代表了覆盖在 图层旋转之后的整个轴对齐的矩形区域,也就是说 frame 的宽高可能 和 bounds 的宽高不再一致了
在这里插入图片描述

锚点

图层的 anchorPoint 通过 position了 控制它的frame 的位置,你可以认为anchorPoint是用来移动图层的把柄。

默认来说, anchorPoint 位于图层的中点,所以图层的将会以这个点为中心放 置。 anchorPoint 属性并没有被 UIView 接口暴露出来,这也是视图的position 属性被叫做“center”的原因。但是图层的 anchorPoint 可以被移动,比如你可以把 它置于图层 frame 的左上角,于是图层的内容将会向右下角的 position 方向移动(图3.3),而不是居中了。
在这里插入图片描述
图层左上角是{0, 0},右下角是{1, 1},因此默认坐标是{0.5, 0.5}。 anchorPoint 可以通过指定x和y 值小于0或者大于1,使它放置在图层范围之外。

坐标系

和视图一样,图层在图层树当中也是相对于父图层按层级关系放置,一个图层 的 position 依赖于它父图层的 bounds ,如果父图层发生了移动,它的所有子图层也会跟着移动。
CALayer

给不同坐标系之间的图层转换提供了一些工具类方法:

- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;

- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;

- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;

- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;

这些方法可以把定义在一个图层坐标系下的点或者矩形转换成另一个图层坐标系下 的点或者矩形.
UIView也有类似方法

Z坐标轴

和UIView严格的二维坐标系不同, CALayer 存在于一个三维空间当中。除了 我们已经讨论过的 position 和 anchorPoint 属性之外, CALayer 还有另外两个属性,zPosition和anchorPointZ,二者都是在Z轴上描述图层位置的浮点类型。
通常,图层是根据它们子图层的 sublayers 出现的顺序来类绘制的,这就是所谓的画家的算法–就像一个画家在墙上作画–后被绘制上的图层将会遮盖住之前的图 层,但是通过增加图层的 zPosition ,就可以把图层向相机方向前置,于是它就 在所有其他图层的前面了(或者至少是小于它的 zPosition 值的图层的前面)
我们希望在真实的应用中也能显示出绘图的顺序,同样地,如果我们提高绿色视图的 zPosition ,我们会发现顺序就反了。其实并不需要增加太多,视图都非常地薄,所以给 zPosition 提高一个像素就可以让绿色视图前置,当然0.1或者0.0001也能够做到,但是最好不要这样,因为浮点类型四舍五入的计算可能会造成一些不便的麻烦

本来绿色块在红色块之下,现在我们想要调换顺序只需设置zPOsition的值

greenView.layer.zPosition = 1.0f;

在这里插入图片描述
但其实绿色块只是绘制在红色块之前,打开图层树,顺序并没有变,所以并不会影响事件传递的顺序

总结

讲了这么多CALayer,但其实跟UIView是分不开的,了解越多,对于他两的关系体会越深吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值