图层的树状结构
CoreAnimation
其实是一个令人误解的命名。你可能认为它只是用来做动画的,但实际上它是从一个叫做LayerKit
这么一个不怎么和动画有关的名字演变而来,所以做动画这只是CoreAnimation
特性的冰山一角。
CoreAnimation
是一个复合引擎,它的职责就是尽可能快地组合屏幕上不同的可视内容,这个内容是被分解成独立的图层,存储在一个叫做图层树的体系之中。于是这个树形成了UIKit
以及在iOS应用程序当中你所能在屏幕上看见的一切的基础。
图层与视图
UIView
在iOS当中,所有的视图都从一个叫做UIVIew
的基类派生而来,UIView
可以处理触摸事件,可以支持基于CoreGraphics
绘图,可以做仿射变换(例如旋转或者缩放),或者简单的类似于滑动或者渐变的动画。
CALayer
CALayer
类在概念上和UIView
类似,同样也是一些被层级关系树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置。它们有一些方法和属性用来做动画和变换。和UIView
最大的不同是CALayer
不处理用户的交互。
每一个UIview
都有一个CALayer
实例的图层属性,也就是所谓的backing layer
,视图的职责就是创建并管理这个图层,以确保当子视图在层级关系中添加或者被移除的时候,他们关联的图层也同样对应在层级关系树当中有相同的操作。
实际上这些背后关联的图层才是真正用来在屏幕上显示和做动画,UIView
仅仅是对它的一个封装,提供了一些iOS类似于处理触摸的具体功能,以及CoreAnimation
底层方法的高级接口。
UIView和CALayer的区别
-
UIView
可以响应事件,CALayer
不可以。 -
UIView
主要是对显示内容的管理而CALayer
主要侧重显示内容的绘制。 - 在做 iOS 动画的时候,修改非
RootLayer
的属性(譬如位置、背景色等)会默认产生隐式动画,而修改UIView
则不会。
图层的能力
如果说CALayer
是UIView
内部实现细节,那我们为什么要全面地了解它呢?苹果当然为我们提供了优美简洁的UIView
接口,那么我们是否就没必要直接去处理CoreAnimation
的细节了呢?
某种意义上说的确是这样,对一些简单的需求来说,我们确实没必要处理CALayer
,因为苹果已经通过UIView
的高级API间接地使得动画变得很简单。
但是这种简单会不可避免地带来一些灵活上的缺陷。如果你略微想在底层做一些改变,或者使用一些苹果没有在UIView
上实现的接口功能,这时除了介入CoreAnimation
底层之外别无选择。
CALayer的属性
布局
-
frame
frame
不支持隐式动画,所以通常使用bounds
和position
来设置layer
的位置和大小。 -
bounds
内部坐标。 -
position
代表了相对于父图层anchorPoint
所在的位置。
锚点
-
anchorPoint
图层的position
属性都指定了anchorPoint
相对于父图层的位置。你可以认为anchorPoint
是用来移动图层的把柄。锚点的取值范围为(0~1,0~1),标示此点相对于宽高的比例,默认点(0.5,0.5),但是图层的
anchorPoint
可以被移动,比如你可以把它置于图层frame
的左上角,于是图层的内容将会向右下角的position
方向移动,而不是居中了。锚点的位置对图层的位置以及某些动画的重心起到决定性的作用,通过锚点计算图层位置,中心点相对于父父层的位置永远不变,锚点于中心点重合。以旋转动画为例,锚点是旋转的中心。
蒙版
-
mask
mask
的作用就相当于PS中的蒙版,在一些图层处理上具有很大的优势(注:蒙版透明色为过滤掉指定layer
的区域内容,不透明色为显示制定图层的区域内容)。
寄宿图
-
contents
这个属性的类型被定义为id,意味着它可以是任何类型的对象。在这种情况下,你可以给contents
属性赋任何值,你的app仍然能够编译通过。但是,在实践中,如果你给contents
赋的不是CGImage
,那么你得到的图层将是空白的。layer.contents = (__bridge id)image.CGImage;
-
contentGravity
当你设置UIImageView
时,如果加载的图片并不刚好符合UIImageView
的大小,为了适应这个视图,图片就会被拉伸,解决方法就是把contentMode
属性设置成更合适的值。CALayer
与contentMode
对应的属性叫做contentsGravity
,但是它是一个NSString
类型,而不是像对应的UIKit
部分,那里面的值是枚举。contentsGravity
可选的常量值有以下一些:kCAGravityCenter kCAGravityTop kCAGravityBottom kCAGravityLeft kCAGravityRight kCAGravityTopLeft kCAGravityTopRight kCAGravityBottomLeft kCAGravityBottomRight kCAGravityResize kCAGravityResizeAspect kCAGravityResizeAspectFill
-
contentsScale
contentsScale
属性定义了寄宿图的像素尺寸和视图大小的比例,默认情况下它是一个值为1.0的浮点数。contentsScale
的目的并不是那么明显。它并不是总会对屏幕上的寄宿图有影响。如果你尝试对我们的例子设置不同的值,你就会发现根本没任何影响。因为contents
由于设置了contentsGravity
属性,所以它已经被拉伸以适应图层的边界。当用代码的方式来处理寄宿图的时候,一定要记住要手动的设置图层的
contentsScale
属性,否则你的图片在Retina
设备上就显示得不正确啦。那是因为和UIImage
不同,CGImage
没有拉伸的概念。当我们使用UIImage
类去读取我们的图片的时候,他读取了高质量的Retina
版本的图片。但是当我们用CGImage
来设置我们的图层的内容时,拉伸这个因素在转换的时候就丢失了。 maskToBounds
现在图片显示了正确的大小,但是他超出了视图的边界。UIView
有一个叫做clipsToBounds
的属性可以用来决定是否显示超出边界的内容,CALayer
对应的属性叫做masksToBounds
,把它设置为YES,超出的部分就裁剪掉了。-
contentsRect
CALayer
的contentsRect
属性允许我们在图层边框里显示寄宿图的一个子域。这涉及到图片是如何显示和拉伸的,所以要比contentsGravity
灵活多了。和
bounds
,frame
不同,contentsRect
不是按点来计算的,它使用了单位坐标,单位坐标指定在0到1之间,是一个相对值(像素和点就是绝对值)。所以他们是相对与寄宿图的尺寸的。 -
contentsCenter
看名字你可能会以为它可能跟图片的位置有关,不过这名字着实误导了你。contentsCenter
其实是一个CGRect
,它定义了一个固定的边框和一个在图层上可拉伸的区域。 改变contentsCenter
的值并不会影响到寄宿图的显示,除非这个图层的大小改变了,你才看得到效果。他工作起来的效果和
UIImage
里的-resizableImageWithCapInsets:
方法效果非常类似,只是它可以运用到任何寄宿图,甚至包括在CoreGraphics
运行时绘制的图形。