前言html
当咱们使用核心动画时,Layer对象是一切的核心。Layers 管理咱们APP的可视化content,Layer也提供了content样式及content可视化的外观的调整选项。尽管iOSAPP自动支持Layer,可是OS XAPP必须明确开启Layer的使用才能利用这些相关的性能特色。一旦开启Layer的使用,咱们须要去理解如何配置和操做Layers才能获得想要的效果。后端
为APP开启核心动画的支持缓存
在iOS APP里,核心动画是一直支持的而且每一个view 都关联一个Layer(这种view被称为layer-backed view)。在OSX APP中,必须手动开启核心动画的支持,关联QuartzCore framework。(iOS APP必须关联这个framework仅当使用核心动画接口时候)。app
开启Layer的支持后,建立layer-backed view是其功能之一,这种View,系统将会负责为其建立Layer对象并保持那个Layer的更新。框架
调整和View关联的Layeriview
Layer-backed view 默认建立一个CALayer类的实例,大多数状况下咱们不须要其余样式的Layer对象。然而,核心动画提供了其余的Layer类,各个Layer类都具备特殊的功能。选择对应的Layer类有助于咱们提升性能,或者有助于以一种更简单的方式显示特殊样式的content。例如CATiledLayer类为高效的展现大图而优化设计的。ide
改变Uiview的Layer类性能
咱们可以改变iOS View 的Layer类别经过重写layerClass方式并一个不一样的类。许多iOS Views建立一个CALayer对象并使用该对象做为content的存储。对于许多咱们本身的Views,默认的layer类别就是一个不错的选择。可是在某些特定的场景别的Layer类是更合适的。例如,咱们可能想要去改变Layer类在一下场景:优化
·咱们的View绘制content经过使用Metal 或者OpenGL ES,此时咱们须要使用CAMetalLayer 或者CAEAGLLayer 对象。动画
·当有特定的Layer类提供更好的性能。
·咱们想要利用某些特定的核心动画Layer类别。例如粒子发射或者粒子复制
改变View的Layer类的方式是很简明的;就像2-1所示同样,咱们须要作的只是重写LayerClass方法而且返回咱们想要使用的类对象在展现以前View将会先调用layerClass方法,并使用它返回的类对象建立一个Layer对象,一旦建立后,该view的layer对象讲不可更换。
+ (Class) layerClass { return [CAMetalLayer class]; }
相应的Layer类别的列表,和如何使用它们,参见 Different Layer Classes Provide Specialized Behaviors。
各类Layer类提供对应的特性
核心动画定义了许多标准的Layer类,每个都具备特定的用途。CALayer是根类,它定义的特性其余全部的Layer都必须支持,CALayer也是默认的类别。固然,咱们也可使用2-1表中的Layer类。
Table 2-1 CALayer
subclasses and their uses
Class |
Usage |
---|---|
实现核心动画中粒子发射系统,发射器Layer控制粒子的生成和他们的原点。 |
|
用来画颜色的渐变,以便于用渐变的颜色填充layer上面的形状(通常指绘制的图形)。 |
|
用于设置和渲染可绘制的纹理(用于渲染layer 的content经过使用Metal) |
|
用于设置后端存储和渲染layer 的content的上下文(OpenGL ES或者OpenGL ) |
|
当想要自动拷贝一个或多个子layer时,复制器建立副本,并使用咱们指定的属性改变外观或者副本的属性。 |
|
用于管理一个由许多子layer复合而成的可滚动的区域。 |
|
用于画一个三维或者二位的贝塞尔曲线。这种layer在绘制基于path形状方面是有利的,由于他们总会建立出一个完善的path;而咱们将path绘制到某个layer的后台存储的,看起来将会有瑕疵当缩放的时候。然而,这个完善的性能牵涉到在主线程渲染该形状并缓存这个结果。(我的以为他应该是保存的矢量图) |
|
用于渲染文本中普通的或者属性字符串。 |
|
用于管理可以被分割成许多小块并单独渲染的大图,并支持缩小和放大content. |
|
用于渲染真实的3D layer 图层(而不是其余layer类别所展示的平面图层) |
|
用于渲染一个Quartz Composer 合成品 (OS X only)。 |
为Layer提供contents
Layers是管理(由APP提供)content的数据对象。一个Layer的content由包含要被展示的可视化数据的bitmap所组成。咱们能够经过如下三种方式提供bitmap:
·直接将一个image对象关联到Layer对象的content属性上。(对于不多或者不会改变的Layer content而言,这是最好的方式。)
·为Layer关联一个代理并让这个代理绘制Layer的content。(对于可能按期或者偶尔改变的Layer content,或者要经过某个对象提供contnent,例如view,这是最好的方式。)
·定义一个Layer的子类而且重写他的绘制方法以便提供Layer的contents。(若是咱们不得不建立一个Layer的子类,或者若是咱们想要去改变Layer基础的绘制行为,这将是最合适的方法。)
当咱们单首创建Layer对象时,也是惟一咱们须要考虑怎么为Layer提供content的时候,若是咱们的APP只包含layer-backed view,咱们没必要考虑前面提到的如何提供Layer content的方法。Layer-backed views将会尽量的以最有效的方式为他们关联的Layer提供contents。
将一个image做为Layer的content
由于一个Layer仅仅是一个管理bitmap image的容器,因此咱们直接为Layer的contnent属性关联一个image。将一个图片关联到Layer是很便捷的,而且能让咱们轻松的指定一个图片显示到屏幕上,Layer使用咱们直接提供大的Image,而且将不会建立那个图片的副本。当咱们的APP在多个地方使用同一个图片,这个方式能够节约内存。
咱们关联到Layer的图片必须是一个CGImageRef类型。(在OSX v10.6和以后,咱们也能够关联NSImage对象。)当关联某个图片的时候,记住要提供和设备的分辨率相匹配的图片。对于Retina屏幕的设备而言,咱们须要调整图片的contentsScale属性,更多关于为Layer提供高分辨率contnet能够参见 Working with High-Resolution Images。
经过代理为Layer提供content
若是咱们的Layer须要动态改变,当须要改变的时候,咱们可使用代理对象来提供或者更新content,在显示的时候,Layer经过调用代理的方法来提供所须要的content。
·若是代理实现了displayLayer:方法,那么该方法的实现体须要为建立一个bitmap并将该bitmap关联到Layer的content属性中。
·若是代理实现了 drawLayer:inContext:方法,核心动画将会建立bitmap,以及建立用于绘制bitmap的图形context,并调用这个代理方法填充bitmap,此代理方法须要作的就是将所需内容绘制到图形context中。
代理对象必须实现displayLayer: 或者drawLayer:incontext:方法,若是代理同时实现了这两个方法,那么代理仅仅只会执行displayLayer:方法。
当咱们的APP须要加载或者建立要显示的bitmap的时候,重写displayLayer:方法是最合适的方法。代码清单2-3展现了displayLayer:代理方法的简单实现过程,在这个例子中,代理对象使用一个协助对象来加载所须要的image。代理方法选择显示哪一个image取决于它自身的状态变量,这个变量在例子中就是这得自定义的属性displayYesImage。
Listing 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];
}
}
若是咱们没有预先渲染bitmap,或者也没有供建立bitmaps的协助对象,那咱们须要经过drawLayer:incontext:方法实现动态绘制content。代码清单2-4展现了drawLayer:incontext:方法的实现。在这个例子中,代理使用设定的宽度和渲染颜色绘制了一个简单的曲线path。
Listing 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); }
对于须要自定义content的layer-backed views而言,咱们仍然须要充写view的方法来实现绘制。一个Layer-backed view将他的Layer的代理关联给他自己,并实现了所需的代理方法,并且咱们也不须要手动改变这个配置,不过,咱们须要实现这个layer-backed view的drawRect:方法来绘制咱们的content。
经过子类提供Layer 的content
若是咱们须要实现一个自定义的Layer,咱们能够重写Layer的绘制方法作各类各样的绘制。不一样于Layer对象自身生成自定义的content,layer须要管理这个用于显示的content(这句话应该是指的是相对于指定image提供content的方式)。例如,CATiledLayer类管理一个大的image的原理是:经过讲image分隔成许多小的能够单独管理和绘制的片断,因为这个Layer有关于在什么时候须要渲染那一个片断的信息,他直接管理了整个绘制行为。
当自定义子类的时候,咱们须要实现下面两种绘制content的方式之一:
·重写子类的display方法,并直接在该方法中设置contents属性。
·重写子类的drawInContext:方法而且使用他在该图形context中进行绘制。
选择重写那种方法取决于咱们须要控制绘制过程的程度。display方法是对于更新Layer的content的彻底控制,所以重写该方法将使咱们彻底控制绘制的过程。重写display方法也意味着咱们须要建立CGImageRef并将其关联到content属性。若是咱们仅仅想要绘制content(或者让咱们的layer管理绘制操做),咱们能够重写drawInContext:方法,并让Layer建立后备存储。
对咱们提供的content进行调整
当将一个image关联到Layer的content属性时候,Layer的contentGravity属性决定了那个图片将被如何调整为了适应当前的bounds。默认的若是一个图片是比当前的bounds更大或者更小的,Layer对象就会缩放这个图片以便适应可用的空间,若是Layerbounds的宽高比是不一样于image的宽高比,这将会引发图片显示效果不彻底,咱们可使用contentsGravity属性来确保content以最好的方式展示。
可供使用的contentsGravity类型被分为两大种:
·基于position的重力常量,使咱们能够在无缩放的状况下,从bounds的某个边或者角铺展image。
·基于缩放的重力常量,使咱们能够以某几种方式之一伸缩image,有些选项能够维持宽高比,有些将不维持image的原有宽高比。
图2-1展现了基于点的重力设置对image的影响。除了kCAGravityCenter常量的使用,其余的常量将会以image bounds的某个边或者角来铺展image。kCAGravityCenter常量将image从中心开始铺展。这类常量不会引发图片的伸缩,所以image会按照原来的尺寸进行渲染。若是image是大于Layer的bounds,这将会致使image一部分被裁减掉,若是image的尺寸是小于Layer的bounds,若是设置了layer的背景色,空缺的部分将会显示Layer的背景色。
Figure 2-1 Position-based gravity constants for layers
图2-2展现了基于缩放的重力常量如何影响image显示的。若是image的尺寸和Layer的bounds不一致,全部的这些常量将会缩放image。这些常量的不一样之处在于如何调整这些图片原来的宽高比,其中有些模式是保持原来的宽高比,还有一些将会改变原来的宽高比。默认状况下Layer的contentsGravity属性是被设置为kCAGravityResize 常量,这是该类型中惟一不保持图片原有宽高比的图片。
Figure 2-2 Scaling-based gravity constants for layers
如何使用高分辨率的Images
Layers自己并不知道所在设备屏幕分辨率。Layer仅仅存储指向bitmap的指针并使用所给定的可利用的像素按照尽量好的方式展示。若是咱们将一个Image关联到一个Layer的content属性,咱们必须经过设置Layer的contentScale属性告诉核心动画Image的分辨率。ContentScale属性的默认值是1.0,这只适用于那些将要显示到标准分辨率屏幕的Image,若是咱们的Image想要显示到Retina屏幕上,那么咱们就必须设置这个属性为2.0。
若是咱们直接关联一个bitmap到Layer,那么咱们就须要设置contentScale的属性。为了适配屏幕的分辨率和被View管理的content,UIKit 和AppKit框架中 layer-backed view会自动设置Layer的缩放因子。例如,在OSX中,若是咱们关联一个NSImage对象到Layer的contnet属性,AppKit将会查找标准和更高分辨率的Image,若是都有,那么AppKit将会使用正确的分辨率的image来设置contentScal的属性。
调整Layer的可视化风格和展现样式
Layer对象能够建立可视化的配件来增添Layer的主要contents,例如边框和背景色。这些可视化的配件不须要咱们本身作任何的渲染工做,所以在某些状况下就可使用Layer做为单独的总体。全部须要咱们作的仅仅是设置Layer的属性,Layer将会处理所需的绘制工做,固然也包含任何动画。对于可视化配件是如何影响Layer显示的说明能够参见 Layer Style Property Animations。
Layer有他们本身的背景和边框
除了他的基于image的content以外,一个Layer能够显示填充的背景和填充的边框。背景色是在Layer的content后面渲染的,边框是在image的content的上面渲染的,就像图2-3所示。若是Layer包含子Layer,他们也出如今边框的下面。因为背景色是放置在image的后面的,image的透明的部分将会显示背景色。
Figure 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的背景色,包括带有透明份量的颜色或者使用样品image。当使用样品image的时候,要注意Core Graphics 处理图片的渲染,以及在此处理图片渲染的过程当中将使用标准坐标系统,标准坐标系统和iOS中默认的坐标系统是不一样的。在iOS上默认状况下,图片渲染是上下颠倒的,除非咱们调整坐标系统。
若是咱们设置Layer的背景色为不透明的颜色,那么咱们应该讲Layer的opaque属性设置为YES,这么作将会提高性能当合成该Layer到屏幕上显示时候,并消除了layer 做为辅助存储时候管理的透明通道。若是Layer有非0的圆角,咱们就没必要将该Layer标记为不透明。
Layer支持圆角
经过添加corner radius,咱们能够为Layer建立圆角效应。corner radius是一个可视化配件,它能够遮盖Layer的四角让下面的content显示出来。就像图2-4显示的。它涉及到透明遮罩的应用,corner radius不影响image的Layer中的content属性,除非设置masksToBounds属性为YES。然而,corner radiu 一直影响Layer的背景色和边框的绘制。
Figure 2-4 A corner radius on a layer
为了将corner radius应用到Layer,咱们须要为Layer的cornerRadius属性指定一个值。圆角的指定单位是points,而且显示的时候,它将会预先应用到Layer的四个角。
Layer支持内建阴影
CALayer 类包含几个为配置阴影效应的属性。阴影会经过让它看起来像是漂浮在content下面的方式来为Layer添加深度。这是另一种可视化的配件,当APP须要的时候可使用。对于Layer而言咱们能够控制阴影的颜色,相对于content的位置,不透明度,和形状。
Layer阴影的不透明度默认被设置为0,这将会有效的隐藏阴影。改变一个透明度为非零的值,将会引发核心动画绘制阴影。阴影默认是被直接放置在Layer的下面的,为了可以看到阴影,咱们也须要调整阴影的偏移量。有个很重要的事情须要记住,咱们为阴影指定的偏移量是基于Layer的本地坐标系统的,也就是说在iOS和OSX 上面是不一样的。如图2-5展现了一个偏向右下的阴影,在iOS中,须要指定一个正数在Y轴份量上,可是在OSX 上就须要指定为负值。
Figure 2-5 Applying a shadow to a layer
当为Layer添加阴影的时候,阴影就是Layer的content的一部分,可是实际上阴影有可能超出Layer的边界,若是开启Layer的maskToBounds属性那么阴影的效应将会在Layer的边界处被裁剪,这将会产生一个奇怪的现象,那就是在Layer的内部的阴影是显示的,可是在Layer的边界外部的阴影缺看不到。若是咱们想要显示完整的阴影而且还想使用bounds masking,咱们就应该使用两个Layer而不是一个,将mask应用到包含content的Layer上,而后将这个Layer嵌入到第二个Layer上,第二个Layer的尺寸和第一个的尺寸同样,第二个Layer上设定阴影效果。
更多关于如何Layer上如使用阴影可参见Shadow Properties。
为Layer添加自定义属性
CAAnimation 和CALayer 类延伸了KVC以便支持自定义属性。咱们能够其使用这个特性为Layer添加数据,而后经过自定义的属性获取该数据。咱们甚至能够关联actions到自定义的属性中,进而达到当修改这个属性的时候,对应的动画将会被执行。
为更多的信息关于如何使用和设置自定义属性,能够参见 Key-Value Coding Compliant Container Classes。为更多的信息关于添加actions到Layer对象,能够参见 Changing a Layer’s Default Behavior。
打印Layer-backed View的内容
在打印期间,Layer 会重绘他们的contents为了适配打印环境。核心动画正常状况下依靠缓存的bitmap当渲染到屏幕上的时候,然而他将会重绘制content当打印的时候。尤为,layer-backed view使用drawRect:方法提供Layer 的content,当打印的时候,核心动画将再次调用drawRect:来生成须要打印的Layer的content。