CoreAnimation编程指南

第一章 核心动画概念

核心动画是一套包含图形绘制,投影,动画的Objective–C类集合。它通过开发人员所熟悉的应用程序套件和Cocoa Touch视图架构的抽象分层模式,同时使用先进的合作效果提供了一套流畅的动画。
动态的动画接口很难创建,但是核心动画通过提供如下接口使这些创建起来变得更加简单:
简单易用的高性能混合编程模型。
类似视图一样,你可以通过使用图层来创建复杂的接口。
轻量级的数据结构,它可以同时显示并让上百个图层产生动画效果。
一套简单的动画接口,可以让你的动画运行在独立的线程里面,并可以独立于主线程之外。
一旦动画配置完成并启动,核心动画完全控制并独立完成相应的动画帧。
提高应用性能。应用程序只当发生改变的时候才重绘内容。再小的应用程序也需要改变和提供布局服务层。核心动画还消除了在动画的帧速率上运行的应用程序代码。
灵活的布局管理模型。包括允许图层相对同级图层的关系来设置相应属性的位置和大小。
使用核心动画,开发人员可以为他们的应用创建动态用户界面,而无需使用低级别的图形API,如OpenGL来获取高效的动画性能。

1.1 核心动画类

核心动画类有以下分类:
提供显示内容的图层类。
动画和计时类。
布局和约束类。
事务类,在原子更新的时候组合图层类。
核心动画的基础类包含在Quartz核心框架(Quartz Core framework)里面,虽然它的其他图层类在其他框架里面定义。下图显示了核心动画的类层次结构。
图 1 Core Animation 类结构

1.1.1 图层类(Layer Classes)

图层类是核心动画的核心基础,它提供了一套抽象的概念(假如你使用过NSView或者UIView的话,你一定会对它很熟悉)。CALayer是整个图层类的基础,它是所有核心动画图层类的父类。

和视图类(NSView或UIView)一样,CALayer有自己的父图层类,同时也拥有自己子图层类的集合,它们构成了一个图层树的层次结构。图层绘制类似视图一样自底向上,并指定其几何形状相对他们superlayer,创建一个相对的局部坐标系。然而图层类通过合并变换矩阵允许你旋转、偏移、拉伸对应的图层内容。具体内容将会在后面“图层(Layer)的几何和变换”部分介绍。

CALayer从Application Kit 和Cocoa Touch的视图类分离出来,因为没有必要为了显示内容而继承CALayer类。因为CALayer类的内容显示可以通过以下方法提供:
可以直接或者委托的方式把图层的内容属性设置为Core Graphics image。

  • 提供直接绘制到一个Core Graphics image上下文委托。
  • 设置所有图层所具有的可视化样式属性,比如背景颜色、不透明属性、蒙版等。
  • Mac OS X应用同样可以通过核心图像滤镜来访问它的可视化样式属性。
  • 继承CALayer并通过封装方法实现以上任何技术。
  • “提供图层内容”描述了提供内容层可用方法。可视化样式样式属性和它们的顺序将会在 “图层样式属性”部分详细介绍。

除了CALayer类,核心动画类同时提供了显示其他内容的类。这些类在Mac OS X 和 iOS上有细微的差别,以下类在Mac OS X和iOS上都可用:

  • CAScrollLayer 是CALayer的子类,简化显示图层的一部分内容。CAScrollLayer对象的滚动区域的范围在它的子图层里面定义。CAScrollLaye不提供键盘或鼠标事件处理,也不提供可见的滚动条。
  • CATextLayer 可以方便的从字符串或字符串的内容创建一个图层类的内容。
  • CATiledLayer 允许递增的显示大而复杂的图片。

Mac OS X 提供如下额外的类:

  • CAOpenGLLayer 提供了一个OpenGL渲染环境。你必须继承这个类来使用OpenGL提供的内容。内容可以是静态的,或可随着时间的推移更新。
  • QCCompositionLayer (由Quartz框架提供)可以把Quartz合成的内容动画显示。
  • QTMovieLayer and QTCaptureLayer (QTKit框架提供)提供播放QuickTime影片和视频直播。

iOS 提供如下额外的类:

  • CAEAGLLayer 提供了一个OpenGLES渲染环境。
  • CALayer的类引入键-值编码兼容的容器类概念,也就是说一个类可以使用键 – 值编码的方法存储任意值,而无需创建一个子类。CALayer的还扩展了NSKeyValueCoding的非正式协议,加入默认键值和额外的结构类型的自动对象包装(CGPoint,CGSize,CGRect,CGAffineTransform和CATransform3D)的支持,并提供许多这些结构的关键路径领域的访问。
  • CALayer同时管理与层关联的动画和行为,。图层接受层树的插入和删除层动作,修改层的属性,或者明确的开发请求。这些行为通常会导致动画发生。见“动画”和“图层操作”的更多信息。

1.1.2 动画和计时类

图层的很多可视化属性是可以隐式动画的。通过简单的改变图层的可动画显示的属性,可以让图层现有属性从当前值动画渐变到新的属性值。例如设置图层的hidden属性为YES将会触发动画使层逐渐淡出。大多数动画属性拥有自己关联的默认动画,你可以轻松地定制和替换。我们将会在后面“动画属性”部分列出一个完整的动画属性列表和它们相应的默认动画。动画的属性也可以显式动画。要显式动画的属性,你需要创建核心动画动画类的一个实例,并指定所需的视觉效果。显式动画不会改变该属性的值,它只是用于动画显示。

核心动画的动画类使用基本的动画和关键帧动画把图层的内容和选取的属性动画的显示出来。所有核心动画的动画类都是从CAAnimation类继承而来。CAAnimation实现了CAMediaTiming协议,提供了动画的持续时间,速度,和重复计数。CAAnimation也实现了CAAction协议。该协议为图层触发一个动画动作提供了提供标准化响应。
动画类同时定义了一个使用贝塞尔曲线来描述动画改变的时间函数。例如,一个匀速时间函数(linear timing function)在动画的整个生命周期里面一直保持速度不变,而渐缓时间函数(ease-out timing function)则在动画接近其生命周期的时候减慢速度。

核心动画额外提供了一系列抽象的和细化的动画类,比如:

  • CATransition 提供了一个图层变化的过渡效果,它能影响图层的整个内容。动画进行的时候淡入淡出(fade)、推(push)、显露(reveal)图层的内容。这些过渡效果可以扩展到你自己定制的Core Image滤镜。
  • CAAnimationGroup 允许一系列动画效果组合在一起,并行显示动画。
  • CAPropertyAnimation 是一个抽象的子类,它支持动画的显示图层的关键路径中指定的属性
  • CABasicAnimation 简单的为图层的属性提供修改。
  • CAKeyframeAnimation支持关键帧动画,你可以指定的图层属性的关键路径动画,包括动画的每个阶段的价值,以及关键帧时间和计时功能的一系列值。在动画运行是,每个值被特定的插入值替代。

核心动画 和 Cocoa Animation 同时使用这些动画类。使用动画描述,是因为这些类涉及到核心动画,这些将会在Animation Types and Timing Programming Guide 有较深入的讨论。

1.1.3 布局管理器类

Application Kit的视图类相对于superlayer提供了经典的“struts and springs”定位模型。图层类兼容这个模型,同时 Mac OS X上面的核心动画提供了一套更加灵活的布局管理机制,它允许开发者自己修改布局管理器。核心动画的 CAConstraint 类是一个布局管理器,它可以指定子图层类限制于你指定的约束集合。每个约束(CAConstraint类的实例封装)描述层的几何属性(左,右,顶部或底部的边缘或水平或垂直中心)的关系,关系到其同级之一的几何属性层或superlayer。
通用的布局管理器和约束性布局管理器将会在“布局核心动画的图层”部分讨论。

1.1.4 事务管理类

图层的动画属性的每一个修改必然是事务的一个部分。CATransaction是核心动画里面负责协调多个动画原子更新显示操作。事务支持嵌套使用。
核心动画支持两种事务:隐式事务和显式事务。在图层的动画属性被一个线程修改,同时该线程下次迭代的时候自动提交该修改的时候隐式事务自动创建。显式事务发生在程序在修改动画属性之前给CATransaction发送了一个开始消息,在动画属性修改之后提交该消息。
事务管理将会在后面的“事务”部分详细介绍。

虽然核心动画的图层和 Cocoa的视图在很大程度上没有一定的相似性,但是他们两者最大的区别是,图层不会直接渲染到屏幕上。

在模型-视图-控制器(model-view-controller)概念里面NSView和UIView是典型的视图部分,但是在核心动画里面图层是模型部分。图层封装了几何、时间、可视化属性,同时它提供了图层现实的内容,但是实际显示的过程则不是由它来完成。
每个可见的图层树由两个相应的树组成:一个是呈现树,一个是渲染树。下图显示在Mac OS X上面使用核心动画图层类显示一个图层树的例子。

图 1 Core Animation 渲染架构

图层树包含每一层的对象模型值。他们就是你设定的图层的属性值。
呈现树包含了当前动画发生时候将要显示的值,例如你要给图层背景颜色设置新的值的时候,它会立即修改图层树里面相应的值。但是在呈现树里面背景颜色值在将要显示给用户的时候才被更新为新值。
渲染树在渲染图层的时候使用呈现树的值。渲染树负责执行独立于应用活动的复杂操作。渲染由一个单独的进程或线程来执行,使其对应用程序的运行循环影响最小。
在原子动画事务执行过程中,你可以查看一个CALayer的实例。如果你打算改变当前的动画,要当前显示的状态开始新的动画,这将会对你有非常大的帮助。

1.1 图层的坐标系

图层的坐标系在不同平台上面具有差异性。在iOS系统中,默认的坐标系统原点在图层的中心左上角地方,原点向右和向下为正值。在Mac OS X系统中,默认的坐标系原点在图层的中心左下角地方,原点向右和向上为正值。坐标系的所有值都是浮点类型。你在任何平台上面创建的图层都采用该平台默认的坐标系。
每个图层定义并维护自己的坐标系,它里面的全部内容都由此相关的坐标系指定位置。该准则同时适应于图层自己的内容和它的任何子图层。因为任何图层定义了它自己的坐标系,CALayer类提供相应的方法用于从一个图层坐标系的点、矩形、大小值转化为另一个图层坐标系相应的值。
一些基于图层的属性使用单元坐标空间测量它们的值。单元坐标空间指定图层边界的相对值,而不是绝对值。单元坐标空间给定的x和y的值总是在0.0到1.0之间。指定一个沿X轴的值为0.0的点,得到的是图层左边缘的一个点,而指定一个1.0的点,则是图层右边缘的一个点。(对 Y轴而言,如果是在iOS系统,则0.0对应于顶部的点,而1.0则是底部的点,而在Mac OS X系统,得到的刚好相反,就如之前提到的坐标系不同一样)。而点(0.5,0.5)则刚好是图层的中心点。

1.2 指定图层的几何

虽然图层和图层树与视图和视图的结构在很多方面具有相似性,但是图层的几何却不同,它更加简单通俗。图层的所有几何属性,包括图层的矩阵变换,都可以隐式和显式动画。
下图显示可以在上下文中指定图层几何的属性:

图 1 CALayer的几何属性

图层的position属性是一个CGPoint的值,它指定图层相当于它父图层的位置,该值基于父图层的坐标系。
图层的bounds属性是一个CGRect的值,指定图层的大小(bounds.size)和图层的原点(bounds.origin)。当你重写图层的重画方法的时候,bounds的原点可以作为图形上下文的原点。
图层拥有一个隐式的frame,它是position,bounds,anchorPoint和transform属性的一部分。设置新的frame将会相应的改变图层的position和bounds属性,但是frame本身并没有被保存。但是设置新的frame时候,bounds的原点不受干扰,bounds的大小变为frame的大小,即bounds.size=frame.size。图层的位置被设置为相对于锚点(anchor point)的适合位置。当你设置frame的值的时候,它的计算方式和position、bounds、和anchorPoint的属性相关。
图层的anchorPoint属性是一个CGPoint值,它指定了一个基于图层bounds的符合位置坐标系的位置。锚点(anchor point)指定了bounds相对于position的值,同时也作为变换时候的支点。锚点使用单元空间坐标系表示,(0.0,0.0)点接近图层的原点,而(1.0,1.0)是原点的对角点。改变图层的父图层的变换属性(如果存在的话)将会影响到anchorPoint的方向,具体变化取决于父图层坐标系的Y轴。
当你设置图层的frame属性的时候,position会根据锚点(anchorPoint)相应的改变,而当你设置图层的position属性的时候,bounds会根据锚点(anchorPoint)做相应的改变。
iOS 注意:以下示例描述基于Mac OS X的图层,它的坐标系原点基于左下角。在iOS上面,图层的坐标系原点位于左上角,原点向下和向右为正值。这变化用具体数值显示,而不是概念描述。
下图描述了基于锚点的三个示例值:

图 2 三个锚点值

anchorPoint默认值是(0.5,0.5),位于图层边界的中心点(如上图显示),B点把anchorPoint设置为(0.0,0.5)。最后C点(1.0,0.0)把图层的position设置为图层frame的右下角。该图适用于Mac OS X的图层。在iOS系统里面,图层使用不同的坐标系,相应的(0.0,0.0)位于左上角,而(1.0,1.0)位于右下角。
图层的frame、bounds、position和anchorPoint关系如下图所示:

图 3 图层原点 :基于(0.5,0.5)

在该示例中,anchorPoint默认值为(0.5,0.5),位于图层的中心点。图层的position值为(100.0,100.0),bounds为(0.0,0.0,120,80.0)。通过计算得到图层的frame为(40.0,60.0,120.0,80.0)。
如果你新创建一个图层,则只有设置图层的frame为(40.0,60.0,120.0,80.0),相应的position属性值将会自动设置为(100.0,100.0),而bounds会自动设置为(0.0,0.0,120.0,80.0)。
下图显示一个图层具有相同的frame(如上图),但是在该图中它的anchorPoint属性值被设置为(0.0,0.0),位于图层的左下角位置。

图 4 图层原点 :基于 (0.0,0.0)

图层的frame值同样为(40.0,60.0,120.0,80.0),bounds的值不变,但是图层的position值已经改变为(40.0,60.0)。
图层的几何外形和Cocoa视图另外一个不同地方是,你可以设置图层的一个边角的半径来把图层显示为圆角。图层的cornerRadius属性指定了重绘图层内容,剪切子图层,绘制图层的边界和阴影的时候时候圆角的半径。
图层的zPosition属性值指定了该图层位于Z轴上面位置,zPosition用于设置图层相对于图层的同级图层的可视位置。

1.3 图层的几何变换
图层一旦创建,你就可以通过矩阵变换来改变一个图层的几何形状。CATransform3D的数据结构定义一个同质的三维变换(4×4 CGFloat值的矩阵),用于图层的旋转,缩放,偏移,歪斜和应用的透视。
图层的两个属性指定了变换矩阵:transform和sublayerTransform属性。图层的transform属性指定的矩阵结合图层的anchorPoint属性作用于图层和图层的子图层上面。图 3显示在使用anchorPoint默认值(0.5,0.5)的时候旋转和缩放变换如何影响一个图层。而图 4显示了同样的矩阵变换在anchorPoint为(0.0,0.0)的时候如何改变一个图层。图层的sublayerTransform属性指定的矩阵只会影响图层的子图层,而不会对图层本身产生影响。
你可以通过以下的任何一个方法改变CATransform3D的数据结构:
使用CATransform3D函数
直接修改数据结构的成员
使用键-值编码改变键路径
CATransform3DIdentity是单位矩阵,该矩阵没有缩放、旋转、歪斜、透视。把该矩阵应用到图层上面,会把图层几何属性修改为默认值。

1.3.1 变换函数

使用变换函数可以在核心动画里面在操作矩阵。你可以使用这些函数(如下表)去创建一个矩阵一般后面用于改变图层或者它的子图层的transform和sublayerTransform属性。变换函数或者直接操或者返回一个CATransform3D的数据结构。这可以让你能够构建简单或复杂的转换,以便重复使用。
表 1 CATransform3D 变换函数 :偏移、旋转和缩放

Function
Use
CATransform3DMakeTransla tion
Returns a transform that translates by ‘(tx, ty, tz)’. t’ = [1 0 0 0; 0 1 0 0; 0 0 1 0; tx ty tz 1].
CATransform3DTranslate
Translate ‘t’ by ‘(tx, ty, tz)’ and return the result: * t’ = translate(tx, ty, tz) * t.
CATransform3DMakeScale
Returns a transform that scales by `(sx, sy, sz)’: * t’ = [sx 0 0 0; 0 sy 0 0; 0 0 sz 0; 0 0 0 1].
CATransform3DScale
Scale ‘t’ by ‘(sx, sy, sz)’ and return the result: * t’ = scale(sx, sy, sz) * t.
CATransform3DMakeRotatio n
Returns a transform that rotates by ‘angle’ radians about the vector ‘(x, y, z)’. If the vector has length zero the identity transform is returned.
CATransform3DRotate
Rotate ‘t’ by ‘angle’ radians about the vector ‘(x, y, z)’ and return the result. t’ = rotation(angle, x, y, z) * t.

旋转的单位采用弧度(radians),而不是角度(degress)。以下两个函数,你可以在弧度和角度之间切换。
CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;};
CGFloat RadiansToDegrees(CGFloat radians) {return radians * 180 / M_PI;};

核心动画 提供了用于转换矩阵的变换函数CATransform3DInvert。一般是用反转点内转化对象提供反向转换。当你需要恢复一个已经被变换了的矩阵的时候,反转将会非常有帮助。反转矩阵乘以逆矩阵值,结果是原始值。
变换函数同时允许你把CATransform3D矩阵转化为CGAffineTransform(仿射)矩阵,前提是CATransform3D矩阵采用如下表示方法。
表 2 CATransform3D 与 CGAffineTransform 转换

Function
Use
CATransform3DMakeAffineT ransform
Returns a CATransform3D with the same effect as the passed affine transform.
CATransform3DIsAffine
Returns YES if the passedCATransform3D can be exactly represented an affine transform.
CATransform3DGetAffineTr ansform
Returns the affine transform represented by the passedCATransform3D.

变换函数同时提供了可以比较一个变换矩阵是否是单位矩阵,或者两个矩阵是否相等。
表 3 CATransform3D 相等测试
Function
Use
CATransform3DIsIdentity
Returns YES if the transform is the identity transform.
CATransform3DEqualToTran sform
Returns YES if the two transforms are exactly equal..

1.3.2 修改变换的数据结构

你可以修改CATransform3D的数据结构的元素为任何其他你想要的数据值。代码1包含了CATransform3D数据结构的定义,结构的成员都在其相应的矩阵位置。

代码 1 CATransform3D structure
struct CATransform3D

1
2
3
4
5
6
7
8
{
CGFloat m11, m12, m13, m14;
CGFloat m21, m22, m23, m24;
CGFloat m31, m32, m33, m34;
CGFloat m41, m42, m43, m44;
};

typedef  struct CATransform3D CATransform3D;

代码2中的示例说明了如何配置一个CATransform3D一个角度变换。
代码 2 直接修改CATransform3D数据结构

1

1.3.3 通过键值路径修改变换

核心动画扩展了键-值编码协议,允许通过关键路径获取和设置一个图层的CATransform3D矩阵的值。表4描述了图层的transform和sublayerTransform属性的相应关键路径。
表 4 CATransform3D key paths

Field Key Path
Description
rotation.x
The rotation, in radians, in the x axis.
rotation.y
The rotation, in radians, in the y axis.
rotation.z
The rotation, in radians, in the z axis.
rotation
The rotation, in radians, in the z axis. This is identical to setting the rotation.z field.
scale.x
Scale factor for the x axis.
scale.y
Scale factor for the y axis.
scale.z
Scale factor for the z axis.
scale
Average of all three scale factors.
translation.x
Translate in the x axis.
translation.y
Translate in the y axis.
translation.z
Translate in the z axis.
translation
Translate in the x and y axis. Value is an NSSize or CGSize.

你不可以通过Objective-C 2.0的属性来设置结构域的值,比如下面的代码将会无法正常运行:
myLayer.transform.rotation.x=0;

替换的办法是,你必须通过setValue:forKeyPath:或者valueForKeyPath:方法,具体如下:
[myLayer setValue:[NSNumber numberWithInt:0] forKeyPath:@”transform.rotation.x”];

图层不但给自己提供可视化的内容和管理动画,而且充当了其他图层的容器类,构建图层层次结构。
本章介绍了图层层次结构,以及如何操纵该图层层次结构。

1.1 什么是图层树的层次结构
图层树是核心动画里面类似Cocoa视图的层次结构。比如一个NSView或者UIView的实例拥有父视图(superview)和子视图(subview),一个核心动画的图层拥有父图层(suplayer)和子图层(sublayer)。图层树和视图结构一样提供了很多便利:
复杂的接口可以由简单的图层来组合,避免了硕大和复杂的继承化子类。图层非常合适于这种堆叠方式来合成复杂的功能。
每个图层定义了一个基于其父图层的坐标系的坐标系。当一个图层变换的时候,它的子图层同样变换。
一个动态的图层树,可以在程序运行的时候重新设置。图层可以创建并添加为一个图层的第一个子图层,然后从其他图层的图层树上面删除。

1.2 在视图里面显示图层
核心动画不提供在一个窗口(window)实际显示图层的手段,它们必须通过视图来托管。当视图和图层一起的时候,视图为图层提供了底层的事件处理,而图层为视图提供了显示的内容。
iOS上面的视图系统直接建立在核心动画的图层上面。每个UIView的实例会自动的创建一个CALayer类的实例,然后把该实例赋值给视图的layer属性。你可以在需要的时候向视图的图层里面添加子图层。
在Mac OS X,您必须配置一个NSView的实例,通过这样一种方式才可以让它托管图层。为了显示图层树的根图层,你可以设置一个视图的图层和配置视图以便使用图层:
代码 1 向view中插入layer
// theView is an existing view in a window
// theRootLayer is the root layer of a layer tree

[theView setLayer: theRootLayer];
[theView setWantsLayer:YES];

1.3 从图层结构里面添加和删除图层
简单的实例化一个图层并不意味已经把它插入了一个图层树。而是通过以下表1的方法来实现从图层树里面添加、插入、替换和删除图层。
表 1 图层树的管理函数
Method

Result
addSublayer:
Appends the layer to the receiver’s sublayers array.
insertSublayer:atIndex:
Inserts the layer as a sublayer of the receiver at the specified index.
insertSublayer:below:
Inserts the layer into the receiver’s sublayers array, below the specified sublayer.
insertSublayer:above:
Inserts the layer into the receiver’s sublayers array, above the specified sublayer.
removeFromSuperlayer
Removes the receiver from the sublayers array or mask property of the receiver’s superlayer.
replaceSublayer:with:
Replaces the layer in the receiver’s sublayers array with the specified new layer.

你也可以通过使用一个图层的数组来设置图层的子图层,甚至可以扩展设置父图层的sublayers属性。当把图层的sublayers属性设置了一个图层的数组值的时候,你必须保证数组里面每个图层的父图层已经被设置为nil。
默认情况下从一个可视化图层树里面插入和删除图层将会触发动画。当把一个图层添加为子图层的时候,将会触发父图层返回标识符为kCAOnOrderIn动画。当从图层的子图层里面删除一个图层的时候,将会触发父图层返回一个标识符为kCAOnOrderOut的动画。当替换图层的子图层里面的一个图层的时候,将会触发父图层返回一个标识符为KCATransition的动画。当你操作图层树的时候,你可以禁用动画或者改变使用任何标识符的动画。

1.4 图层的位置调整和大小改变
图层创建以后,你可以通过改变图层的几何属性:frame、bounds、position、anchorPoint和zPosition来编程式移动和改变图层大小。
如果一个图层的属性needsDisplayOnBoundsChange被设置为YES的时候,当图层的bounds属性改变的时候,图层的内容将会被重新缓存起来。默认情况下图层的needsDisplayOnBoundsChange属性值为NO。
默认情况下,设置图层的属性frame、bounds、position、anchorPoint和zPosition属性将会导致图层动画显示新值。

1.4.1 自动调整图层大小

CALayer提供了一个机制,在父图层被移动或者改变大小的时候,子图层可以自动的跟着移动和调整大小。在很多情况下简单的配置一个图层的自动调整掩码(autoresizing mask)可以适当的适应程序的行为。
一个图层的自动调整掩码可以通过指定CAAutoresizingMask的常量结合或运算(OR)所得的结果赋值给图层的autoresizingMask属性值。表2列举了掩码常量和这些掩码如何影响图层的大小调整行为。
表 2 自动缩放掩码及其解释
Autoresizing Mask
Description

kCALayerHeightSizable
如果设置了,则layer的高度按比例随父layer的高度变化。
kCALayerWidthSizable
如果设置了,则layer的宽度按比例随父layer的宽度变化。
kCALayerMinXMargin
如果设置了,则layer的左边距按比例随父layer的宽度变化。如果未设置,则layer的左边距保持原来相对父layer的位置。
kCALayerMaxXMargin
如果设置了,则layer的右边距按比例随父layer的宽度变化。如果未设置,则layer的右边距保持原来相对父layer的位置。
kCALayerMaxYMargin
如果设置了,则layer的上边距按比例随父layer的宽度变化。如果未设置,则layer的上边距保持原来相对父layer的位置。
kCALayerMinYMargin
如果设置了,则layer的下边距按比例随父layer的宽度变化。如果未设置,则layer的下边距保持原来相对父layer的位置。

例如,为了把保持图层位于它父图层的相对左下角位置,你可以使用kCALayerMaxXMargin | kCALayerMaxYMargin。当沿着一个轴具有多个方向被设置为适应可变的时候,那么调整大小的尺寸为使其均匀分布的值。图1提供了一个常量值的位置的图形表示。
图 1 Layer的自缩放掩码常量

当这些常量里面的任何一个被省略的时候,图层的布局在这个方向上值是固定的。当一个常量包含在图层的自动调整掩码里面的时候,该方向上的图层的布局值是适应可变的。
CALayer的子类可以重写函数resizeSublayersWithOldSize:和resizeWithOldSuperlayerSize:来定制化的自动调整图层大小的行为。图层的函数resizeSublayersWithOldSize:将会在bounds属性被修改的时候自动的触发执行,同时发送一个消息resizeWithOldSuperlayerSize:给图层的每个子图层。图层的每个子图层根据自动调整掩码的属性来比较就的边界值和新的边界值来调整它的位置和大小。

1.5 裁剪子图层

在Cocoa的视图里面,当子视图超出父视图的边界的时候,视图将会被裁剪以适应父视图的大小。图层去掉了这个限制,允许子层全部显示,无论自己相对于父层位置如何。图层的masksToBounds属性决定了是否子图层是否相对父图层裁剪。该属性masksToBounds的默认值为NO,即防止子图层被相对于父图层裁剪。表2显示了当设置图层的masksToBounds属性导致的结果,和它如何影响layerB和layerC的显示。
图 2 masksToBounds 属性的实例值

当我们使用Cocoa的视图的时候,我们必须继承NSView或者UIView并且重载函数drawRect:来显示任何内容。但是CALayer实例可以直接使用,而无需继承子类。因为CALayer是一个键-值编码兼容的容器类,你可以在实例里面存储任意值,所以子类实例化完全可以避免。

1.1 给CALayer提供内容

你可以通过以下任何一种方法指定CALayer实例的内容:
使用包含图片内容的CGImageRef来显式的设置图层的contents的属性。
指定一个委托,它提供或者重绘内容。
继承CALayer类重载显示的函数。
1.1.1 设置contents属性

图层的图片内容可以通过指定contents属性的值为CGImageRef。当图层被创建的时候或者在任何其他时候,这个操作可以在其他实体上面完成(如表3所示)。
代码 1 设定layer的contents属性

1
2
3
4
5
6
7
8
9
10
CALayer *theLayer;

// create the layer and set the bounds and position
theLayer=[CALayer layer];
theLayer.position=CGPointMake(50.0f,50.0f);
theLayer.bounds=CGRectMake(0.0f,0.0f,100.0f,100.0f);

// set the contents property to a CGImageRef
// specified by theImage (loaded elsewhere)
theLayer.contents=theImage;

1.1.2 通过委托提供内容

你可以绘制图层的内容,或更好的封装图层的内容图片,通过创建一个委托类实现下列方法之一:
displayLayer:或drawLayer:inContext:
实现委托重绘的方法并不意味会自动的触发图层使用实现的方法来重绘内容。而是你要显式的告诉一个图层实例来重新缓存内容,通过发送以下任何一个方法setNeedsDisplay或者setNeedsDisplayInRect:的消息,或者把图层的needsDisplayOnBoundsChange属性值设置为YES。
通过委托实现方法displayLayer:可以根据特定的图层决定显示什么图片,还可以更加需要设置图层的contents属性值。下面的例子是“图层的坐标系”部分的,它实现displayerLayer:方法根据state的值设置theLayer的contents属性。子类不需要存储state的值,因为CALayer的实例是一个键-值编码容器。
代码 2 委托方法displayLayer:的实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)displayLayer:(CALayer *)theLayer
{
// check the value of the layer's state key
if ([[theLayer valueForKey:@"state"] boolValue])
{
// display the yes image
theLayer.contents=[someHelperObject loadStateYesImage];
}
else {
// display the no image
theLayer.contents=[someHelperObject loadStateNoImage];
}
}

如果你必须重绘图层的内容,而不是通过加载图片,那你需要实现drawLayer:inContext:方法。通过委托可以决定哪些内容是需要的并使用CGContextRef来重绘内容。
下面的例子是“指定图层的几何”部分内容,它实现了drawLayer:inContext:方法使用lineWidth键值来重绘一个路径(path),返回therLayer。

代码 3 代理方法drawLayer:inContext:的实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)drawLayer:(CALayer *)theLayer
inContext:(CGContextRef)theContext
{
CGMutablePathRef thePath = CGPathCreateMutable();

CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
CGPathAddCurveToPoint(thePath,
NULL,
15.f,250.0f,
295.0f,250.0f,
295.0f,15.0f);

CGContextBeginPath(theContext);
CGContextAddPath(theContext, thePath );

CGContextSetLineWidth(theContext,
[[theLayer valueForKey:@"lineWidth"] floatValue]);
CGContextStrokePath(theContext);

// release the path
CFRelease(thePath);
}

1.1.3 通过子类提供图层的内容

虽然通常情况不需要这样做,但是你仍可以继承CALayer直接重载重绘和显示方法。这个通常发生在你的图层需要定制行为而委托又无法满足需求的时候。
子类可以重载CALayer的显示方法,设置图层的内容为适当的图片。下面的例子是“变换图层的几何”部分的内容,它提供了和“图层的坐标系”例子相同的功能。不同的是子类定义state为实例的属性,而不是根据CALayer的键-值编码容器获取。

代码 4 CALayer display 方法的覆盖示例

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)display
{
// check the value of the layer's state key
if (self.state)
{
// display the yes image
self.contents=[someHelperObject loadStateYesImage];
}
else {
// display the no image
self.contents=[someHelperObject loadStateNoImage];
}
}

CALayer子类可以通过重载drawInContext:绘制图层的内容到一个图形上下文。下面的例子是“修改变换的数据结构”的内容,它和“指定图层的几何”里面实现委托的办法一样产生相同的图片内容。唯一的不同的是实现委托里面的lineWidth和lineColor现在是子类实例的属性。
Listing 5 覆盖layer的drawInContext:方法示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)drawInContext:(CGContextRef)theContext
{
CGMutablePathRef thePath = CGPathCreateMutable();

CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
CGPathAddCurveToPoint(thePath,
NULL,
15.f,250.0f,
295.0f,250.0f,
295.0f,15.0f);

CGContextBeginPath(theContext);
CGContextAddPath(theContext, thePath );

CGContextSetLineWidth(theContext,
self.lineWidth);
CGContextSetStrokeColorW ithColor(theContext,
self.lineColor);
CGContextStrokePath(theContext);
CFRelease(thePath);
}

继承CALayer并且实现其中的重绘方法并不意味重绘会自动发生。你必须显式的促使实例重新缓存其内容,可以通过发送以下任何一个方法setNeedsDisplay或setNeedsDisplayInRect:的消息,亦或者设置图层的needsDisplaOnBoundsChange属性为YES。

1.2 修改图层内容的位置
CALayer的属性contentsGravity允许你在图层的边界内容修改图层的contents图片的位置或者伸缩值。默认情况下,内容的图像完全填充层的边界,忽视自然的图像宽高比。
使用contentsGravity位置常量,你可以指定图片位于图层任何一个边界,比如位于图层的角落,或者图层边界的中心。然而当你使用位置常量的时候,contentsCenter属性会被忽略。表1列举了位置常量和他们相应的位置。

表 1 layer的contentsGravity属性的定位常量
Position constant
Description
kCAGravityTopLeft
Positions the content image in the top left corner of the layer.
kCAGravityTop
Positions the content image horizontally centered along the top edge of the layer.
kCAGravityTopRight
Positions the content image in the top right corner of the layer.
kCAGravityLeft
Positions the content image vertically centered on the left edge of the layer.
kCAGravityCenter
Positions the content image at the center of the layer.
kCAGravityRight
Positions the content image vertically centered on the right edge of the layer.
kCAGravityBottomLeft
Positions the content image in the bottom left corner of the layer.
kCAGravityBottom
Positions the content image centered along the bottom edge of the layer.
kCAGravityBottomRight
Positions the content image in the top right corner of the layer.

“图层的坐标系”标识了所支持的内容位置和他们相应的常量。
图 1 layer的contentsGravity属性的定位常量

通过设置contentsGravity属性为其他一个常量(如表2所示)。图层的内容图片可以被向上或者向下拉伸, 仅当使用其他任何一个调整大小的常量的时候,contentsCenter属性才会对内容图片起作用。

表 2 Layer的 contentsGravity 属性的缩放常量
Scaling constant
Description
kCAGravityResize
Resize the content image to completely fill the layer bounds, potentially ignoring the natural aspect of the content. This is the default.
kCAGravityResizeAspect
Resize the content image to scale such that it is displayed as large as possible within the layer bounds, yet still retains its natural aspect.
kCAGravityResizeAspectFill
Resize the content image to scale such that it is displayed filling the layer bounds, yet retaining its natural aspect. This may cause the content to extend outside the layer bounds.
“变换图层的几何”演示了如何使用调整大小的模式来调整一个正方形图像的大小让其适应图层的方形边界。
图 2 Layer的 contentsGravity 属性的缩放常量

注意:使用任何常量kCAGravityResize、kCAGravityResizeAspect和kCAGravityResizeAspectFill和表1中的重心位置常量无关。图层的内容将会填充整个边界,所以使用这些常量无法改变图层内容的位置。

动画是当今用户界面的关键因素。当使用核心动画的时候,动画是自动完成的。没有动画的循环和计数器。你的应用程序不负负责重绘,也不负责跟踪动画的当前状态。动画在独立线程里面自动执行,没有和你的应用程序交互。
本章提供了对动画类的概览,和介绍如何创建隐式的和显式的动画。

1.1 动画类和时序
核心动画提供了一套你可以在你应用程序里面使用的动画类的表现:
CABasicAnimation提供了在图层的属性值间简单的插入。
CAKeyframeAnimation提供支持关键帧动画。你指定动画的一个图层属性的关键路径,一个表示在动画的每个阶段的价值的数组,还有一个关键帧时间的数组和时间函数。
CATransition提供了一个影响整个图层的内容过渡效果。在动画显示过程中采用淡出(fade)、推出(push)、显露(reveal)图层的内容。 常用的过渡效果可以通过提供你自己定制的核心图像滤镜来扩展。
除了要指定显示的动画类型,你还必须指定动画的间隔、它的速度(它的插值如何分布在整个动画过程)、动画循环时候的循环次数、动画周期完成的时候是否自动的反转、还有动画结束的时候它的可视化状态。动画类和CAMediaTiming协议提供所有这些功能甚至更多的功能。
CAAnimation、它的子类、时序协议被核心动画和Cocoa Animation Proxy功能共享。这些类将会在“动画类型和时序编程指南(Animation Types and Timing Programming Guide)”里面详细介绍。

1.2 隐式动画
核心动画的隐式动画模型假定所有动画图层属性的变化应该是渐进的和异步的。动态的动画场景可以在没有显式的动画图层时候实现。改变可动画显示的图层的属性将会导致图层隐式把图层从旧的值动画显示为新的值。虽然动画是持续的,但是设置新的目标值时会导致图层从当前状态动画过渡到新的目标值。
代码1显示了如果简单的触发一个隐式的动画,把图层从当前位置动画改变到新的位置。
代码 1 隐式的动画改变图层的position属性
//假设layer当前position为(100.0,100.0)
theLayer.position=CGPointMake(500.0,500.0);

你可以隐式的一次动画改变图层的一个或者多个属性。你还可以隐式的同时动画改变多个图层。代码2的代码实现了4个同时触发的隐式动画。
代码 2 隐式的同时动画改变多个图层的多个属性
// 在移动至远处时将Layer的opacity属性渐进至0
theLayer.opacity=0.0;
theLayer.zPosition=-100;

//在移动至近处时将Layer的opacity属性渐进至1
anotherLayer.opacity=1.0;
anotherLayer.zPosition=100.0;

隐式动画使用动画属性中默认指定的动画时间,除非该时间已经被隐式或者显式的修改过。阅读“重载覆盖隐式动画时间”获取更多详情。

1.3 显式动画

核心动画同时提供了一个显式的动画模型。该显式动画模型需要你创建一个动画对象,并设置开始和结束的值。显式动画不会开始执行,直到你把该动画应用到某个图层上面。代码3中的代码片段创建了一个显式动画,它实现一个层的不透明度从完全不透明过渡到完全透明的,3秒后返回重新执行。动画没有开始,直到它被添加到某一图层层。
代码 3 显式动画
CABasicAnimation *theAnimation;

theAnimation=[CABasicAnimation animationWithKeyPath:@"opacity"];
theAnimation.duration=3.0;
theAnimation.repeatCount=2;
theAnimation.autoreverses=YES;
theAnimation.fromValue=[NSNumber numberWithFloat:1.0];
theAnimation.toValue=[NSNumber numberWithFloat:0.0];
[theLayer addAnimation:theAnimation forKey:@"animateOpacity"];

显式动画对于创建连续执行的动画非常有帮助。代码4显示了如何创建一个显式动画,把一个CoreImage滤镜应用到图层上面,动画显示其强度。这将导致“选择的图层”跳动,吸引用户的注意力。
代码 4 连续显式动画示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// The selection layer will pulse continuously.
// This is accomplished by setting a bloom filter(梦维:用5.0和5.1模拟器测试发现CIBloom这个名称无法初始化滤镜,返回值为nil) on the layer
// create the filter and set its default values
CIFilter *filter = [CIFilter filterWithName:@"CIBloom"];
[filter setDefaults];
[filter setValue:[NSNumber numberWithFloat:5.0] forKey:@"inputRadius"];

// name the filter so we can use the keypath to animate the inputIntensity
// attribute of the filter
[filter setName:@"pulseFilter"];

// set the filter to the selection layer's filters
[selectionLayer setFilters:[NSArray arrayWithObject:filter]];

// create the animation that will handle the pulsing.
CABasicAnimation* pulseAnimation = [CABasicAnimation animation];

// the attribute we want to animate is the inputIntensity
// of the pulseFilter
pulseAnimation.keyPath = @"filters.pulseFilter.inputIntensity";

// we want it to animate from the value 0 to 1
pulseAnimation.fromValue = [NSNumber numberWithFloat: 0.0];
pulseAnimation.toValue = [NSNumber numberWithFloat: 1.5];

// over a one second duration, and run an infinite
// number of times
pulseAnimation.duration = 1.0;
pulseAnimation.repeatCount = HUGE_VALF;

// we want it to fade on, and fade off, so it needs to
// automatically autoreverse.. this causes the intensity
// input to go from 0 to 1 to 0
pulseAnimation.autoreverses = YES;

// use a timing curve of easy in, easy out..
pulseAnimation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEa seInEaseOut];

// add the animation to the selection layer. This causes
// it to begin animating. We'll use pulseAnimation as the
// animation key name
[selectionLayer addAnimation:pulseAnimation forKey:@"pulseAnimation"];

1.4 开始和结束显式动画
你可以发送addAnimation:forKey:消息给目标图层来开始一个显式动画,把动画和标识符作为参数。一旦把动画添加到目标图层,动画将会一直执行直到动画完成,或者动画被从图层上面移除。把动画添加到图层时添加的标识符,同样也可以在停止动画的时候使用,通过调用removeAnimationForKey:。你可以通过给图层发送一个removeAllAnimations消息来停止图层所有的动画。

图层的行为在以下情况发生的时候被触发:从图层树里面插入或者删除一个图层,图层的属性值被修改了,或者程序显式要求。通常情况下,行为触发器是动画显示的结果所在。

1.1 行为对象的角色
一个行为对象是一个通过CAAction协议响应行为标识符的对象。行为标识符使用标准圆点分隔的关键路径来命名。图层负责把行为标识符映射到特定的行为对象。当一个特定标识符的行为对象被确定的时候,它会发送一个CAAction协议定义的消息。
CALayer类提供了默认的CAAnimation的行为对象实例,一个兼容类所有动画层属性CAAction协议。表1中CALayer同样定义了以下没有直接对应到属性的行为触发器和他们的行为标识符。
表 1 Action 触发器和相应的标示符
Trigger
Action identifier
一个layer被插入一个可见的layer树,或者layer的hidden属性被设为NO
kCAOnOrderIn
一个layer被从一个可见的layer树中移除,或者layer的hidden属性被设为YES
kCAOnOrderOut
使用replaceSublayer:with:方法将一个可见树中的layer替换
kCATransition

1.2 已定义搜索模式的行为键值
当一个行为触发器发生的时候,图层的actionForKey:方法被调用。此方法返回一个行为对象,对应的标识符作为参数,或如果行为对象不存在的话返回nil。
当CALayer为一个标识符实现的actionForKey:方法被调用的时候,以下的搜索模式将会被用到:
如果一个图层有委托,那方法actionForLayer:forKey:的实现将会被调用,把图层和行为标识符作为参数。委托的actionForLayer:forKey:的实现需要响应如下:
返回一个行为标识符对应的行为对象。
返回nil,当无法处理行为标识符的时候。
返回NSNull,当无法处理行为标识符,而且搜索需要被终止的时候。
图层的actions字典被搜索以便找到一个和行为标识符对应的对象。
图层的style属性被搜索以便找到一个包含行为标识符的actions字典。
图层类发生一个defaultActionForKey:的消息。它将会返回一个和标识符对应的行为对象,如果不存在的话则返回nil。

1.3 采用CAAction协议
CAAction协议定义了行为对象如何被调用。实现CAAction协议的类包含一个方法runActionForKey:object:arguments:。
当行为对象收到一个runActionForKey:object:arguments:的消息时,行为标识符、行为发生所在的图层、额外的参数字典会被作为参数传递给方法。
通常行为对象是CAAnimation的子类实例,它实现了CAAction协议。然而你也可以返回任何实现了CAAction协议的类对象。当实例收到runActionForKey:object:arguments:的消息时,它需要执行相应的行为。
当CAAnimation实例受到消息runActionForKey:object:arguments:的时候,它把自己添加到图层的动力里面,触发动画的执行(查看代码1)。
代码 1 runActionForKey:object:arguments: 的实现:启动动画

1
2
3
4
5
6
- (void)runActionForKey:(NSString *)key
object:(id)anObject
arguments:(NSDictionary *)dict
{
[(CALayer *)anObject addAnimation:self forKey:key];
}

1.4 重载隐式动画

你可以为行为标识符提供隐式的动画,通过插入一个CAAnimation的实例到style字典里面的actions的字典里面,通过实现委托方法actionForLayer:forKey:或者继承图层类并重载defaultActionForKey:方法返回一个相应的行为对象。
代码2的示例通过委托替换contents属性的隐式动画。
代码 2 contents 属性的隐式动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (id)actionForLayer:(CALayer *)theLayer
forKey:(NSString *)theKey
{
CATransition *theAnimation=nil;

if ([theKey isEqualToString:@"contents"])
{

theAnimation = [[CATransition alloc] init];
theAnimation.duration = 1.0;
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEa seIn];
theAnimation.type = kCATransitionPush;
theAnimation.subtype = kCATransitionFromRight;
}

return theAnimation;
}

代码3的示例使用actions字典模式禁用sublayers属性的默认动画。
代码 3 sublayers 属性的隐式动画

1
2
3
4
5
6
7
8
// get a mutable version of the current actions dictionary
NSMutableDictionary *customActions=[NSMutableDictionary dictionaryWithDictionary :[theLayer actions]];

// add the new action for sublayers
[customActions setObject:[NSNull null] forKey:@"sublayers"];

// set theLayer actions to the updated dictionary
theLayer.actions=customActions;

1.5 暂时禁用行为

默认情况下,你任何时候改变一个可动画显示的属性时,相应的动画将会伴随发生。
在修改图层属性的时候,你可以通过使用事务暂时禁用行为。查看“暂时禁用图层的行为”部分来获取更多信息。

图层的每个改变都是事务的一部分。CATransaction是核心动画类,它负责成批的把多个图层树的修改作为一个原子更新到渲染树。
本章介绍了核心动画支持的两种事务。隐式事务和显式事务。

1.1 隐式事务

当图层树被没有获得事务的线程修改的时候将会自动创建隐式事务,当线程的运行循环(run-loop)执行下次迭代的时候将会自动提交事务。
代码1的示例修改图层的opacity,zPosition和position数字,依赖隐式事务来确保动画同时一起发生。
代码 1 隐式事务动画

theLayer.opacity=0.0;
theLayer.zPosition=-200;
thelayer.position=CGPointMake(0.0,0.0);

重要:当在一个没有运行循环(runloop)的线程修改图层的属性的时候,你必须使用显式的事务。

1.2 显式事务

在你修改图层树之前,可以通过给CATransaction类发送一个begin消息来创建一个显式事务,修改完成之后发送comit消息。显式事务在同时设置多个图层的属性的时候(例如当布局多个图层的时候),临时禁用图层的行为,或者临时修改动画的时间的时候非常有用。
1.2.1 临时禁用图层的action

你可以在修改图层属性值的时候通过设置事务的kCATransactionDisableActions值为YES来暂时禁用图层的行为。在事务范围所作的任何更改也不会因此而发生的动画。代码2显示了一个示例,当把aLayer从可视化图层树移除的时候禁用淡出动画。
代码 2 临时禁用 layer的action

1
2
3
4
5
6
7
8
9
[CATransaction begin];

[CATransaction setValue:(id)kCFBooleanTrue

forKey:kCATransactionDisableAct ions];

[aLayer removeFromSuperlayer];

[CATransaction commit];

1.2.2 重载隐式动画的时间

你可以暂时改变响应改变图层属性的动画的时间,通过设置事务的kCATransactionAnimationDuration键的值为新的时间。事务范围内所产生的任何动画都会使用该新设置的时间值而不是他们原有的值。代码3显示了一个示例,把动画的发生时间改为10秒而不是zPosition和opacity所指定的动画的默认时间。
代码 3 重写动画持续时间

1
2
3
4
5
6
7
8
9
10
11
[CATransaction begin];

[CATransaction setValue:[NSNumber numberWithFloat:10.0f]

forKey:kCATransactionAnimationD uration];

theLayer.zPosition=200.0;

theLayer.opacity=0.0;

[CATransaction commit];

即使上面的示例中显示了begin和commit所包围的显式事务的时间,你也可以忽略这些而采用隐式事务来替代。

1.2.3 事务的嵌套

显式事务可以被嵌套,允许你禁用部分动画的行为或者在属性被修改的时候产生的动画使用不同的时间。仅当最外层的事务被提交的时候,动画才会发生。
代码4 中显示了一个嵌套两个事务的例子。最外层的事务设置隐式动画的时间为2秒,并设置图层的position属性值。内层的事务设置隐式动画的时间为5秒,并修改图层的opacity和zPosition属性值。

代码 4 嵌套的显式事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[CATransaction begin]; // outer transaction

// change the animation duration to 2 seconds
[CATransaction setValue:[NSNumber numberWithFloat:2.0f]
forKey:kCATransactionAnimationD uration];
// move the layer to a new position
theLayer.position = CGPointMake(0.0,0.0);

[CATransaction begin]; // inner transaction
// change the animation duration to 5 seconds
[CATransaction setValue:[NSNumber numberWithFloat:5.0f]
forKey:kCATransactionAnimationD uration];

// change the zPosition and opacity
theLayer.zPosition=200.0;
theLayer.opacity=0.0;

[CATransaction commit]; // inner transaction

[CATransaction commit]; // outer transaction

NSView提供了经典的“stuts and springs”模式,用于视图调整大小的时候把关联到它父图层的视图重新调整位置。图层支持该模式,而且Mac OS X上面的核心动画提供了一个更通用的布局管理器机制,允许开发者自己写他们自己的布局管理器。可以为图层定制一个布局管理器(它通常实现CALayoutManager协议),负责给图层的子图层提供布局功能。
本章介绍了约束布局管理器和如何配置一系列约束条件。
iOS 注意:iOS的CALayer类仅提供“stuts and springs”模式,不提供定制的布局管理器。然而如果你想人工修改关联到特别视图的图层的位置的话,你可以重载相应视图的layoutSubviews方法,在这里面实现你定制的布局代码。你可以查看“iOS视图编程指南(View Programming Guide for iOS)”来获取更多关于如何在iOS应用里面基于视图的布局方法。

1.1 约束布局管理器
基于条件的布局允许你根据图层和它同级图层或者它的父图层的相应关系指定图层的位置和大小。通过CAConstraint类描述的关系被保存在子图层的constraints数组属性里面。
图1描述了在指定关系的的时候你可以使用的布局特性。
Figure 1 布局管理器constraints属性

当使用约束布局的时候,你首先创建一个CAConstraintLayoutManager的实例,并把它设置为父图层的布局管理器。然后你通过实例化CAConstraint对象为子图层创建约束条件,并把这些约束条件通过使用addConstraint:方法添加到子图层的约束属性里面。每个CAConstraint实例封装了一个两个图层在同一轴上的几何关系。
同级层引用的名称,使用图层的name属性。特定的名称superlayer被使用来引用图层的父图层。
每个轴上面最多只能指定两个关系。如果你给图层的左边和右边都指定约束关系,那么图层的宽度就会不同。如果你给图层的左边和宽度指定约束关系,则图层的右边就会从根据父图层的frame移动。通常你一般只会指定单个边的约束条件,图层在同一个轴上面的大小将会作为第二个约束关系。
代码1里面的示例代码创建了一个图层,然后使用位置约束条件添加子图层。图2描述了布局的结果。

Figure 2 基于示例中constraints的布局

代码 1 配置 layer的 constraints

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// create and set a constraint layout manager for theLayer
theLayer.layoutManager=[CAConstraintLayoutManage r layoutManager];

CALayer *layerA = [CALayer layer];
layerA.name = @"layerA";

layerA.bounds = CGRectMake(0.0,0.0,100.0,25.0);
layerA.borderWidth = 2.0;

[layerA addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidY
relativeTo:@"superlayer"
attribute:kCAConstraintMidY]];

[layerA addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidX
relativeTo:@"superlayer"
attribute:kCAConstraintMidX]];

[theLayer addSublayer:layerA];

CALayer *layerB = [CALayer layer];
layerB.name = @"layerB";
layerB.borderWidth = 2.0;

[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintWidth
relativeTo:@"layerA"
attribute:kCAConstraintWidth]];

[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidX
relativeTo:@"layerA"
attribute:kCAConstraintMidX]];


[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxY
relativeTo:@"layerA"
attribute:kCAConstraintMinY
offset:-10.0]];

[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinY
relativeTo:@"superlayer"
attribute:kCAConstraintMinY
offset:+10.0]];

[theLayer addSublayer:layerB];

以上是代码执行过程:

创建一个CAConstraintLayoutmanage r实例,然后把它设置为theLayer的layoutManger的属性。
创建一个CALayer(layerA)的实例,设置图层的name属性为“layerA”。
设置layerA的bounds为(0.0,0.0,10.0,25.0)。
创建一个CAConstraint对象,把它作为约束条件添加到layerA里面。该约束条件是把layerA的水平中心对齐它的父图层的水平中心。
创建第二个CAConstraint对象,把它作为layerA的约束条件。该约束条件是把layerA的垂直中心对齐父图层的垂直中心。
把layerA添加为theLayer的子图层。
创建一个CALayer(layerB)的实例,设置图层的name属性为“layerB”。
创建一个CAConstraint对象,给layerA添加该约束条件,该约束条件是设置layerB的宽度设置为与layerA的宽度相同。
创建第二个CAConstraint对象,把该约束条件添加到layerB里面。该约束条件是设置layerB的水平中心对齐layerA的水平中心。
创建第三个CAConstraint对象,并把它添加为layerB的约束条件。该约束条件设置layerB的顶边低于layerA底边10像素。
创建第四个CAConstraint对象,把它作为约束条件添加到layerB里面。该约束条件是把layerB的底边高于父图层底边10像素。
注意: 有可能创建约束条件导致在相同的属性的循环引用。在布局是无法计算的情况下,行为结果是不可预知的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值