图层的树状结构
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运行时绘制的图形。
