Core Animation 的使用离不开 Layer 对象。Layer 管理着 app 可见的内容,为视觉内容和现实效果提供修改的选项。尽管在 iOS 中默认启用了 layer 功能,然而在 OS X 中要想得到 layer 带来的性能提示就只能手动地启用它。一旦 layer 被启用,就能够尽情的经过配置和操做 app 的 layer 来多的想要的效果了。html
在 iOS 的 app 当中。Core Animation 老是处于启用状态,每个视图都是构建在 layer 之上。而 OS X 就须要按照如下步骤完成 COre Animation 的启用:ios
在前面的 layer 启用方法中,建立的都是 layer-backed 视图。在 layer-backed 视图中,系统会负责建立下面的 layer 对象并监管 layer 对象的更新。在 OS X 中,你能够经过建立 layer-hosting 视图手动建立和管理底层的 layer 对象(在 iOS 中没法建立 layer-hosting 视图)。关于建立 layer-hosting 视图,详见 Layer Hosting Lets You Change the Layer Object in OS X。缓存
Layer-backed 视图默认建立一个 CALayer 类的实例,并且多数时候你并不须要其余类型的 layer 对象。Core Animation 提供了不一样的 layer 类型,每一个类型都提供你可能须要的具体的功能,正确地选择一个不一样的 layer 类型能够用简单的方式帮你提高性能,例如,CATiledLayer 类能够用高效的方式优化大图片的现实。架构
你能够在 iOS 视图中重写 layerClass 方法改变默认的 layer 类型。大部分 iOS 视图建立 CALayer 对象存储它的内容。对于你的视图这个默认选择是有益的,你不须要改变它。可是在特定的状况下你能够找到更合适的 layer 类型。例如,你可能在如下状况中想改变 layer 类型:app
改变视图的 layer 类型很简单,代码 2-1 是一个实例。你须要作的只是重写 layerClass 方法,返回你想替换的类型。在现实以前,视图会调用这个方法,使用返回的类型建立一个新的 layer 对象,一旦建立,就不能在更改。框架
代码 2-1 Specifying the layer class of an iOS viewiview
+ (Class) layerClass{ return [CAEAGLLayer class]; }
关于更多的 layer 类型以及使用,详见 Different Layer Classes Provide Specialized Behaviors。ide
在 NSView 对象中能够重写 makeBackingLayer 方法实现默认 layer 类型的变动。在这个方法的实现里,建立并返回一个你想为视图定义的 layer 对象。你可能会在诸如使用滚动和磁铁 layer 的状况下重写。性能
更多 layer 类型及使用,详见 Different Layer Classes Provide Specialized Behaviors。优化
一个 layer-hosting 视图是一个 NSView 对象,下面的 layer 对象须要你本身手动建立和管理。也许在想控制视图相关类型的状况下你才会使用 layer hosting。例如,你建立一个 layer-hosting 视图以便用一个自定义的 layer 类型来替换默认的 CALayer 类型。也可能会在用单个视图管理不相关 layer 组成层次。
当你调用视图的 setLayer: 方法并提供一个 layer 对象时,AppKit 为这个 layer 提供了方便的操做。一般 AppKit 更新视图的 layer 对象,可是在 layer-hosting 这种状况下它并不为大多数的属性提供更新。
代码 2-2 展现了怎么建立一个 layer-hosting 视图和自定义的 layer 对象,并在屏幕显示前关联二者。另外为了设置layer 对象,你仍需调用 setWantsLayer: 方法以便让视图知道它应该使用 layer:
代码 2-2 Creating a layer-hosting view
// Create myView... [myView setWantsLayer:YES]; CATiledLayer *hostedLayer = [CaTiledLayer layer]; [myView setLayer:hostedLayer]; // Add myView to a view hierachy.
若是你选择了 host layer,你必须设置 contentScale 属性以即可以显示高的分辨率。关于高分辨率的内容和缩放,详见 Working with High-Resolution Images。
Core Animation 定义了许多标准的 layer 类型,每种类型都有特定的用处。CALayer 是这些 layer 对象的根类,它定义了全部 layer 对象都应该支持的行为,是 layer-backed 视图的默认类型。在表 2-1 中展现了可用的 layer 类型:
表 2-1 CALayer subclasses and their uses
Class | Usage |
---|---|
CAEmitterLayer | 用来实现基于 Core Animation 的例子发射系统。发射 layer 对象控制 粒子的原点和生成。 |
CAGradientLayer | 用来绘制被彩色的坡度填充的 layer(具备圆角边界)。 |
CAEAGLLayer/CAOpenGLLayer | 用来在使用 OpenGL ES(iOS) 或 OpenGL(OS X)时存储须要被绘制的上下文。 |
CAPreplicatorLayer | 当你想自动地复制一个或更多的子 layer 时,此对象能够为你复制,并让你改变复制的属性。 |
CAScrollLayer | 用来管理由多个子 layer 构成的大的滚动区域。 |
CAShapeLayer | 能够绘制贝塞尔曲线,能够很方便地绘制基于路径的曲线图形。可是,曲线结果的渲染是在主线程上完成和缓存的。 |
CATextLayer | 用来渲染平面或带有属性的文本字符串。 |
CATiledLayer | 用来管理能够单独渲染或一片片渲染的大图片。 |
CATransformLayer | 用来渲染真是的 3D layer 层次,而不是像其余 layer 类型同样的平面层次。 |
QCCompositionLayer | 用来渲染一个 Quartz COmposer 构造。(仅用于 OS X) |
layer 实际上是管理你 app 内容的数据对象。一个 layer 由一张位图构成,包含了你想显示的可视数据。你能够用一下三种方式为位图提供内容:
由于 layer 只是管理位图的容器,你能够直接赋给 layer 的 contents 属性一张图片。直接赋值一张图片很简单并且让你可以在屏幕上彻底展现你想一想要的内容。layer 会直接使用你提供的图片而不是建立一个图片的拷贝。这种行为能够在多个使用同一张图片的状况下节约内存占用。
你赋值的图片类型必须是 CGImageRef 类型。(OS X v10.6 及以后能够直接使用 NSImage 对象。)同时你也须要注意图片的分辨路问题,在 retina 显示的设备上,可能还须要你适配图片的 contentsScale 属性。具体的layer高分辨率内容详见 Working with High-Resolution Images。
若是你的 layer 内容是动态变化,可使用委托对象根据须要提供内容的更新。在显示时,layer 调用委托方法以提供须要的内容:
委托对象必须实现 displayLayer: 或 drawLayer:inContext: 方法,若是这两个方法都被实现了,layer 只调用 displayLayer: 方法。
当你的 app 想加载或者建立显示的位图时,重写 displayLayer: 方法是最合适的。代码 2-3 展现了一个实现 displayLayer: 委托方法的示例。在这个例子中,委托使用一个帮助对象来加载和显示须要的图片。委托方法根据内部的状态选择显示哪张图片,在这个例子中状态是自定义的属性 displayYesImage。
代码 2-3 Setting the layer contents directly
- (void)displayLayer:(CALayer *)theLayer { // Check the value of some state property if (self.displayYesImage) { // Display the Yes image theLayer.contents = [someHelperObject loadStateYesImage]; } else { // Display the No image theLayer.contents = [someHelperObject loadStateNoImage]; } }
若是你没有预生成的图片,或者不可以建立位图,你的委托可使用 drawLayer:inContext: 方法动态绘制内容。代码 2-4 展现了实现 drawLayer:inContext: 方法的示例。在这个例子中,用固定的款多和当前渲染的颜色绘制了简单的弧形路径。
代码 2-4 Drawing the contents of a layer
- (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, 5); CGContextStrokePath(theContext); // Release the path CFRelease(thePath); }
对于自定义内容的 layer-backed 视图,你应该继续重写视图的方法绘制本身的内容。layer-backed 视图自动实现委托以及委托方法,你不该该更改它的配置,用 drawRect: 方法绘制本身的内容。
在 OS X v10.8 及以后,另外一个绘制的方法是重写你的视图的 wantsUpdateLayer 和 updateLayer 方法提供位图。重写 wantsUpdateLayer 并返回 YES 会使 NSView 类型使用另外一种渲染途径。视图会调用你的 upateLayer 方法代替调用 drawRect:,在方法体中要直接为 layer 的contents 属性赋值一张位图。这是 AppKit 架构指望你直接设置视图 layer 内容的地方。
若是你自定义了一个 layer 类,你能够重写绘制方法定制本身的内容。layer 对象生成独有的内容有点特殊,可是它能管理显示的内容。例如,CATiledLayer 把一张大图分割成一片片来管理,也能够单独显示一片。当 layer 收到须要显示哪一片的信息时它才会直接管理绘制行为。
在继承类中,你能够用如下两种技术绘制本身的 layer 内容:
重写哪一个方法取决于你想怎么控制绘制过程。display 方法是更新 layer 内容的主要入口点,所以重写此方法能够彻底控制内容的显示,同时也意味着你须要在此方法中负责建立 CGImageRef 类型的 contents 属性值。若是你只想绘制内容或者用 layer 管理绘制操做,你能够重写 drawInContext: 方法替代重写 display,在此方法中 layer 为你建立后台存储。
当你为 layer 的 contents 属性赋值一张图片时,layer 的 contentsGravity 属性决定了如何操做这张图片适配当前的界面。若是图片比当前界面大或者小,默认状况下 layer 对象会放大/缩小图片作可用空间的适配。若是 layer 界面的尺寸比和图片的不一样会致使图片扭曲失真。你能够用 contentsGravity 属性确保你的内容以最合适的方式展示。
contentsGravity 属性值分如下两类:
图 2-1 展现了 position-based gravity 设置是如何影响你的图片的。用 kCAGravityCenter 常量,每个均可以把图片固定在 layer 的界面边缘或者边角内。kCAGravityCenter 常量能够居中放置图片。这些常量都不会致使图片缩放失真,所以图片老是以原始尺寸呈现。若果图片的尺寸大于 layer 的界面,则会致使图片的超出部分被剪去,若是图片较小,图片就不能遮盖住 layer 的背景色。
图 2-1 Position-based gravity constants for layers
图 2-2 展现了 scaling-based gravity 常量如何影响图片。若是图片不适配 layer 界面这些常量会缩放图片。这些模式的不一样之处是如何缩放时处理图片的尺寸比,其中有些会保护尺寸比。默认状况下,layer 的 contentsGravity 属性值是 kCAGravityResize 常量,这个常量是惟一不包含图片尺寸比的。
图 2-2 Scaling-based gravity constants for layers
layer 并不会根据设备的分辨率而设置自身的分辨率。layer 只简单地存储一个指向你的位图的指针并以最合适的方式显示有效的像素。若是你为 layer 的contents 属性提供一张图片,你必须设置 layer 的 contentsSale 属性告诉 Core Animation 这张图片的分辨率,此属性的默认值是 1.0,若是在 retina 屏幕上显示就须要设置为 2.0。
只有在直接为 layer 赋值一张位图时才会更改 contentsScale 属性。在 UIKit 和 AppKit 中一个 layer-backed 视图会自动根据屏幕的分辨路和视图管理的内容设置 scale factor 为合适的值。例如,若是你在 OS X 中设置 layer 的contents 属性为一张 NSImage 对象,AppKit 会查找这张图片是否存在普通和高分辨率,若是有都存在,AppKit 会使用正确的一张为当前分辨率设置 contentsScale 属性。
在 OS X 中,position-based gravity 常量影响被赋值给 layer 的 NSImage 对象的图片描述,由于这些常量不会致使图片被缩放,Core Animation 依赖 contentsScale 属性用合适的像素密度显示图片。
在 OS X 中,layer 的委托对象能够实现 layer:shouldInheritContentsScale:fromWindow: 方法,用它来响应缩放相关改变。由于窗口在标准和高分辨率的屏幕间移动,不管给定窗口的分辨率什么时候改变, AppKit 自动调用此方法。若是委托支持 layer 图片分辨率的更改,你实现的这个方法应该返回 YES 值。而后这个方法应该更新 layer 的 contents 以响应新的分辨率。
layer 对象已经内置了一些视觉修饰,如 border 和 背景色,你能够用来修饰 layer 的主要内容。由于这些修饰不须要你坐任何渲染,有时能够把 layer 做为单独的实体使用。你所要作的只是设置一下 layer 的属性,剩下的渲染和动画都是 layer 处理。layer 其余的视觉装饰的效果说明,详见 Layer Style Property Animations
一个 layer 在他基于图片的内容中能够显示填充的背景色和边框加粗。背景色的渲染是在 layer 的图片内容以后,边框加粗则是在上面,如图 2-3 。若是 layer 包含子 layer,也会显示在边框之下。由于背景色在图片之下,要想显示只有设置图片的透明度。
图 2-3 Adding a border and background to a layer
代码 2-5 展现了怎么设置 layer 的背景色和边框。这些属性都是能够造成动画的。
myLayer.backgroundColor = [NSColor greenColor].CGColor; myLayer.borderColor = [NSColor blackColor].CGColor; myLayer.borderWidth = 3.0;
注意:你可使用任意类型的颜色设置 layer 的背景色,包括透明色或者模型图片。当使用模型图片时,Core Graphics 处理模型图片的渲染,使用的是标准的坐标系统,ios 系统的坐标系统不一样于 OS X。所以,在 iOS 中图片的渲染默认是自上而下的,除非你翻转坐标。
若是你要设置 layer 的背景色为不透明色,请考虑设置 layer 的opaque 属性为 YES。这样能够在屏幕上构筑 layer 和消除 layer 的后台存储以管理管道的 alpha 值时提升性能。尽管如此,若是 layer 同时又一个非零的圆角就不用设置为不透明了。
你能够为 layer 添加圆角效果。一个圆角就是 layer 的四个角呈现想通的弧度,如图 2-4 。若是不设置 layer 的属性 masksToBounds 为 YES ,圆角就不会影响 layer cotents 属性的图形内容。然而,圆角老是影响 layer 的背景色和边框的渲染。
图 2-4 A corner radius on a layer
为 layer 的 cornerRadius 指定一个值能够设定其圆角。你设定的半径值的单位是 point,应用于四个边角。
CALayer 类型有许多属性能够配置阴影效果。加载layer 上的阴影效果可使其想浮动在内容之上。在你的 app 中你回发现有时这个装饰效果颇有用。经过 layer,你能够控制阴影的颜色,放置相关的 layer 属性如 content,opacity 和 shape。
layer 的阴影不透明度默认是 0,使阴影不显示。改变不疼名度为非零值可让 Core Animation 绘制出阴影。由于阴影默认是直接放在layer 下面的,在你能看到它以前须要先改变阴影的偏离值。有一点须要注意,你为阴影指定的偏离值使用的是 layer 的本地坐标系统,在 iOS 和 OS X 中是不一样的。图 2-5 展现了一个偏向于右下方的 layer 阴影。在 iOS中,这须要指定 y 轴值为正数,而 OS X 中就须要负数了。
图 2-5 Appling a shadow to a layer
当为 layer 添加阴影时,阴影成为 layer 的一部分但实际却偏离出layer 的边界框。若是你为 layer 启用 masksToBounds 属性,会产生阴影效果被从边缘剪去的结果。若是你的 layer 包含透明的内容,会致使layer 下面的阴影正常显示,可是超出 layer 的则不显示的奇怪效果。若是你既想要有阴影又想使用边界遮盖,可使用两个 layer 替换一个。把内容放在遮掩边界的 layer 中,而后把这个 layer 嵌入到另外一个,另个一 layer 能够实现阴影效果。
关于怎么在 layer 中应用阴影效果,详见 Shadow Properties.
在 OS X 的 app 中,你能够直接为你的 layer 内容应用 Core Image 滤镜。你能够模糊或者锐化 layer 内容,改变颜色,扭曲内容,或者变现其余类型的操做。例如,一个图片处理程序能够非破坏性地使用这些滤镜修改图片,视频编辑程序能够用他们实现不一样的过分类型效果。由于这些滤镜是经过硬件应用在 layer 的内容内,渲染过程很快很平滑。
注意:你不能在 iOS 中为 layer 对象添加滤镜。
对于一个给定的 layer,你能够在layer 的前景和北京内容上应用滤镜。前景内容包含 layer 自身的全部,如 contents 属性的图片,它的背景色,边界,以及子 layer 的内容。背景内容是 layer 下面的内容,但不是 layer 的实际部分。大多数 layer 的背景内容是父 layer 的即时内容,被 layer 彻底或者部分低遮掩。例如,当你想让用户关注 layer 的前景内容时能够设置背景 模糊滤镜。
你能够按照如下的方法为 layer 添加 CIFilter 对象实现滤镜:
为 layer 添加滤镜,首先须要建立和定位一个 CIFilter 对象,而后再添加进 layer 前配置它。CIFilter 类包含几个能够定位到可用 Core Image 滤镜的类方法,好比 filterWithName: 方法。建立滤镜只是第一步。许多滤镜有定义怎么修改图片的参数。例如,一个模糊滤镜须要输入半径参数以便决定要应用的模糊数量。做为滤镜配置进程的一部分你应当提供这些参数值。有一个参数不须要你明确,就是 layer 自身提供的图片。
在添加滤镜到 layer 前,最好先配置好滤镜的相关参数。之因此这样是由于一旦添加进去后就不能对 CIFilter 对象作出修改。可是你能够在加入滤镜以后经过 layer 的setValue:forKeyPath: 方法改变滤镜的值。
代码 2-6 展现了如何为 layer 对象建立和应用一个捏放变形的滤镜。这个滤镜捏放的原像素是 layer 内部的,把这些像素尽量的想中心点靠拢。注意,你不须要明确指出输入的图片,由于 layer 的图片被自动使用。
图 2-6 Applying a filter to a layer
CIFilter* aFilter = [CIFilter filterWithName:@"CIPinchDistortion"];
[aFilter setValue:[NSNumber numberWithFloat:500.0] forKey:@"inputRadius"];
[aFilter setValue:[NSNumber numberWithFloat:1.25] forKey:@"inputScale"];
[aFilter setValue:[CIVector vectorWithX:25.0 Y:150.0] forKey:@"inputCenter"];
myLater.filters = [NSArray arrayWithObject:aFilter];
关于 Core Image 滤镜的更多信息,详见 Core Image Filter Reference。
在 OS X 中,layer-backed 视图为何时候决定更新 layer 的内容提供了几种不一样的策略。由于本地 AppKit 绘制模型和介绍过的Core Animation 存在差别,这些策略可使你的旧代码向 Core Animaiton 迁移更容易。你能够在每个视图中配置这些策略确保获得最好的性能。
每个视图定义一个 layerContentsRedrawPolicy 方法,此方法返回视图 layer 的重绘策略。你能够经过 setLayerContentsRedrawPolicy: 方法设置策略。为了保护和传统绘制模型的兼容性, AppKit 经过 NSViewLayerContentsRedrawDuringViewResize 修改默认的重绘策略。可是,你能够修改策略为表 2-2 中的任意值。注意,推荐的重绘策略并非默认的。
表 2-2 Layer redraw policies for OS X views
Policy | Usage |
---|---|
NSViewLayerContentsRedrawOnSetNeedsDisplay | 这是推荐的方针,使用此方针可使视图几何改变时不会自动致使视图更新 layer 内容。layer 存在的内容被拉伸和操做,以便加快几何的改变。为了能使视图重绘更新 layer 的内容,你必须明确地调用视图的 setNeedsDidsplay: 方法。 此方针比较表明 Core Animation layer 的标准行为。然而,这并非默认的方针并且须要明确地设定。 |
NSViewLayerContentsRedrawDuringViewResize | 这是默认的重绘方针。不管视图几何何时改变,此方针依靠从新缓存 layer 的内容维护和 传统的 AppKit 绘制的最大兼容性。在尺寸操做的过程当中,你的 app 主线程会调用视图的 drawRect: 方法屡次,每次结束时产生此行为。 |
NSViewLayerContentsRedrawBeforeViewResize | AppKit 在位图的尺寸操做和缓存前绘制 layer 的最终尺寸。从新的尺寸操做使用的是已经缓存的位图做为开始的图片,缩放它以便适应就得边框。它最终以动画的形式表现出位图的最终尺寸。这种行为在动画的开始使视图的内容表现为拉伸或失真好过于初始的表现不重要或者不知道关注。 |
NSViewLayerContentsRedrawNever | 使用此方针,即便你调用 setNeedsDisplay: 方法 AppKit 也不会更新 layer。对于那些内容不改变并且尺寸不怎么变化的视图此方针是最合适的。例如,你可能用于固定尺寸的内容或者背景元素。 |
视图的重绘方针缓解了单独子 layer 的频繁使用,提高了绘制的性能。先前介绍的视图重绘方针,有几个会使 layer-backed 的视图绘制的频繁度高于实际须要。根据 OS X v10.6 绘制方针的介绍,强烈推荐你设置 layer-backed 视图的重绘方针,而不是大量的使用 layer 层次构建。
CAAnimation 和 CALayer 类支持经过 key-value 编码扩展自定义的属性。你可使用这个行为为 layer 添加数据,并用自定义的 key 来检索。你甚至能够用自定义的属性关联 actions,以便那你在改变属性时产生响应的动画。
关于如何设置自定义属性,详见 Key-Value Coding Compliant Container Classes。关于为 layer 对象添加 actions 的信息,详见 Changing a Layer's Default Behavior。
在打印期间,layer 按须要重绘他们的内容为打印环境提供便利。因为 Core Animation 通常依赖缓存位图在屏幕上渲染,打印的时候会重绘这些内容。须要特别提出,若是 layer-backed 视图使用 drawRect: 方法提供 layer 的内容,Core Animation 在为了生成打印的 layer 内容的打印期间会再次调用 drawRect: 方法。