核心动画编程指南官方文档笔记(上) Core Animation Programming Guide
PS: 太长了,分成两部整理。上半部是 Core Animation基础知识/如何设置 layer object/如何配置基本的动画
核心动画(Core Animation)是一种在iOS和OSX上都可用的图形渲染和动画的基础框架,你可以使用它来让view和其他虚拟元素具有动画特效。Core Animation替你完成了大部分的动画的绘制每帧的工作。你需要做的是配置一些动画参数(比如开始点和结束点)、通知Core Animation启动它。Core Animation负责鱼虾的工作,处理大部分实际的绘制工作到图形硬件来加速绘制。这个自动图形加速具有高帧率(frame rate)和柔和动态效果,并且不会加重CPU的负担拖慢你的app。
如果你编写iOS Apps,无论你是否了解Core Animation,你都正在使用它。如果你写OSX Apps,你可以通过非常少的代价来利用Core Animation。结构上,它在AppKit和UIKit的底层,并被紧密地联系到Cocoa和Cocoa Touch的视图view工作流程中。当然,Core Animation也有用来扩展你app 视图暴露的功能,让你更深入(fine-grained)细致的控制你app的动画。
Core Animation 管理你App的内容。Core Animation不是一个绘制系统,他是一个在硬件上操作和组合你app内容的基础结构。这个结构的核心是 layer objects,你使用layer objects来管理和操作内容。一个 layer 捕捉你的内容到一个bitmap(可以轻易的图形硬件操作)。在大部分的app中,layers被用来作为管理视图内容的一个方式,但你也能根据需要创建独立的layers。
Layer的修改触法动画。大部分你使用Core Animation创建的动画启动了对layer属性的修改。比如 views:layer objects 有一个矩形方位、一个屏幕上的位置、一个透明属性,一个trasform 变换和许多其他可以被修改的视觉导向的属性。对于大部分这些属性来说,改变属性值导致隐式动画的创建,从而使图层从旧值动画到新值。当你需要更多控制动画行为的时候,你也可以显示改变这些属性。
Layer可以被组织到层级结构中。layer可以被按照层级关系排列来实现类似parent-child 关系。图层的排列类似于视图的方式管理的视觉内容。附加到视图的一组图层的层次结构反映了相应的视图层次结构。你也可以单独的添加layer到一个layer层级中来扩展视觉内容。
Actions让你改变layer的默认行为。隐式layer 动画是由action objects实现的,它是一个实现预定义接口的一半对象。Core Animation 使用action objects 来实现指定给layers的默认动画集合。你可以创建自己的action objects来实现定制动画,或者使用action objects来实现其他类型的行为。只需要把你的action object分配给layer的属性之一。当属性变化时,Core Animation取到你的action对象、启动他的action。
Core Animation 基础
Core Animation 为 动画view和其他视觉元素提供了一个通用系统。Core Animation 不是app views的替代品。相反,他是一种结合视图来提供更好的性能和更好支持动态化内容的技术。通过缓存能被图形硬件直接操作的视图内容到bitmaps来实现。在某些情况下,这种缓存行为可能需要你重新思考如何呈现和管理你app的内容,但大部分情况,你不需要考虑这些,直接使用即可。除了缓存视图内容外,Core Animation还定义了一种指定任意可视内容、将该内容与视图集成、并与其他所有内容一起进行动画制作的方法。
你使用Core Animation来动态化改变你app的视图和视觉对象。大部分改变和修改视觉对象的属性有关。比如:你可能使用Core Animation来动画改变一个view的位置、size、或者透明度。当你做这个修改的时候,Core Animation在当前属性值和你指定的新值之间执行动态化。一般你不用使用Core Animation 60次/秒 替换一个view的内容,比如在一个卡通中。相反,你使用Core Animation围绕着屏幕移动视图内容,淡入或者淡出,实现任意图形变换,或者改变view的其他视觉属性。
Layers 提供了绘制和动画的基础
Layer objects是在3D空间内组织的2D平面,是和所有Core Animation有关操作的核心。像View一样,layers管理关于几何图形,内容和他们的平面的视觉属性等。但是与view不同的是,layers不定义自己的外观显示。一个layer 仅仅管理bitmap周围的状态信息。这个bitmap可以是一个视图的绘制结果或者一个你指定的合适的图片。所以,app中的main layer被认为是model object,因为他们主要管理数据。这是一个重要的要被记住的概念,因为它影响了动画的行为。
基于 Layer的 绘制模型
在APP中,大部分layer 不做实际的绘制工作。一个 layer 拥有你app提供的内容,缓存到一个bitmap中(有时被称为backing store)。当你随后改变这个layer的一个属性时,所有你在做的就是改变指定给layer object的状态信息。当一个改变出发一个动画,Core Animation传递layer的bitmap和状态信息到图形硬件,硬件负责用新信息绘制bitmap,如图,在硬件中操作bitmap 会比在软件中产生更快的动画。
因为硬件操作一个静态的bitmap, 基于layer绘制和更传统的基于view的绘制技术不同。使用view based 绘制,改变view自己经常是导致调用了view的drawrect方法,使用新参数来重绘内容。但是这种绘制方式代价昂贵因为使用了cpu的主线程。Core Animation 可以通过在硬件中操作缓存的位图来避免这种开销,从而达到相同或相似的效果。
尽管Core Animation尽量多的使用缓存内容,但是你app仍然必须提供内容的初始化和经常更新内容。有几种方法,将会在 Providing a layers content里描述。
基于Layer的动画
Layer对象的数据和状态信息和layer屏幕内容的视觉呈现解耦。这种解耦给Core Animation提供了一种方式来插入自己和动态化旧值到新值。比如,改变一个layer的位置引发Core Animation从他的当前位置移动到一个新置顶位置。其他属性导致的动画类似。如图,显示了一些动画类型。
在动画过程中,Core Animation做了所有 frame-by-frame的工作,在硬件中绘制。你只需要指定动画的开始和结束点、让Core Animation做剩下的工作。你也可以指定自定义时机信息和动画属性;但是如果你不需要的话,Core Animation 提供了合适的默认值。
Layer 对象定义自己的集合图形
Layer的工作之一是为自己的内容管理视觉图形。视觉几何图形包括了关于他的屏幕位置和layer是否被rotated,scaled或任何方式的变换等内容范围的信息。和view一样,一个layer有frame和bounds 矩形,你可以使用它来定位layer和他的内容。layers 也有views没有的其他属性,比如一个锚点anchor point(定义了操作发生的点)。你指定layer几何图形的某些方面的方式也与为视图指定该信息的方式不同。
Layers 使用两种坐标系统
图层利用基于点的坐标系和单位坐标系来指定内容的位置。使用哪个坐标系取决于传送的信息的类型。指定直接映射到屏幕坐标的值时必须使用基于点的坐标,或者必须指定相对于其他图层的值(例如图层的位置属性)。当值不应与屏幕坐标相关时使用单位坐标,因为它与其他值相关。例如,图层的anchorPoint属性指定了一个相对于图层本身bounds的点,它可以改变。
基于点的坐标最常见的用途是指定图层的大小和位置,您可以使用图层的bounds和位置属性进行确定。bounds定义了图层本身的坐标系,并包含了图层在屏幕上的大小。 position属性定义了图层相对于其父坐标系的位置。尽管图层具有frame属性,但该属性实际上是从bounds和位置属性中的值导出的,并且使用频率较低。
图层bounds和frame矩形的方向始终与底层平台的默认方向匹配。如图:显示了iOS和OS X上bounds矩形的默认方向。在iOS中,默认情况下,bounds矩形的原点位于图层的左上角,而在OS X中,它位于左下。如果您在iOS和OS X版本的应用程序之间共享Core Animation代码,则必须考虑这些差异。
锚点影响集几何图形操作
与图层的几何图形相关的操作在该图层的锚点发生,您可以使用图层的anchorPoint属性访问该锚点。处理图层的位置或变换属性时,锚点的影响最为明显。位置属性始终是相对于图层的锚点指定的,并且应用于该图层的任何转换也会相对于锚点发生。下图演示了如何将锚点从其默认值更改为不同的值影响图层的position属性。即使图层没有在其父母的范围内移动,将定位点从图层中心移动到图层的边界原点也会更改position属性中的值。
下图 显示了如何更改锚点影响应用于图层的transformation。当您对图层应用旋转变换时,旋转发生在锚点周围。由于默认情况下将锚点设置为图层的中间位置,因此通常会创建您所期望的那种旋转行为。但是,如果更改定位点,则旋转的结果会有所不同。
Layers可以在三维空间中操作
每个layer都有两个transform matrix,你可以用来操作他们和他们的内容。CALayer的transform属性指定了您想要同时应用于层和它的嵌入式子元素的转换。通常,当您想要修改层本身时,您可以使用该属性。例如,您可以使用该属性来缩放或旋转图层,或者暂时改变它的位置。sublayerTransform属性定义了仅应用于子图层的其他转换,并且最常用于将透视视觉效果添加到场景内容。
变换的工作方式是将坐标值乘以数字矩阵以获得代表原始点变换版本的新坐标。由于Core Animation值可以在三维中指定,每个坐标点有四个值,必须通过四乘四矩阵相乘,如图,在Core Animation中,图中的变换由CATransform3D类型表示。幸运的是,您不必直接修改此结构的字段以执行标准transform。 核心动画提供了一套全面的功能,用于创建scale,translation和rotation matrix以及进行矩阵比较。 除了使用函数来操作transform,Core Animation还扩展了kvc支持,允许您使用key path修改变换。 有关可以修改的key path表,请参阅CATransform3D的key path。
下图显示了可以进行的一些更常见转换的矩阵配置。 通过标识变换乘以任何坐标返回完全相同的坐标。 对于其他转换,坐标如何修改完全取决于您更改哪个矩阵组件。 例如,要仅沿着x轴进行平移,您将为平移矩阵的tx分量提供非零值,并将ty和tz值保留为0.对于旋转,您将提供适当的正弦和余弦值 目标旋转角度。
Layer tree反映了动画状态的不同方面
使用Core Animation的应用程序有三组layer objects。 每一组layer objects在使你的应用程序的内容出现在屏幕上时具有不同的作用:
- model layer tree中的对象(或简称为“layer tree”)是与您的应用交互最多的对象。 此树中的对象是存储任何动画的目标值的model对象。 无论何时更改layer的属性,都可以使用这些对象之一。
- presentation tree中的对象包含任何正在运行的动画的过程(flight-in)值。 尽管layer tree对象包含动画的目标值,但presentation tree中的对象反映了屏幕上显示的当前值。 绝不应该修改此树中的对象。 相反,您可以使用这些对象来读取当前的动画值,也可以从这些值开始创建新的动画。
- render tree中的对象 执行实际的动画,是私有的。
每组layer对象都组织成一个层次结构,就像app中的view一样。 事实上,对于为所有view启用layer的应用程序,每个tree的初始结构都与view层次结构完全匹配。 但是,应用程序可以根据需要将其他layer object(即未与view关联的layer)添加到layer分层结构中。 你可以在某些情况下执行此操作,以优化应用程序对不需要的view开销的内容的性能。 下图显示了在简单的iOS应用程序中找到的图层细分。 示例中的窗口包含一个内容视图,该视图本身包含一个按钮视图和两个独立的layer objects。 每个视图都有一个相应的layer object,构成layer层次结构的一部分。
对于layer tree中的每个对象,在presentation tree和render tree中都有一个匹配的对象,如下图所示。 如前所述,应用程序主要处理layer tree中的对象,但有时可能访问presentation tree中的对象。 具体而言,访问layer tree中对象的presentationLayer属性将返回presentation tree中的相应对象。 您可能想要访问该对象以读取动画中间属性的当前值。
重要:只有在动画正在进行时,才应访问presentation tree中的对象。 在动画进行过程中,presentation tree包含当前显示在屏幕上的layer值。 这种行为不同于layer树,它总是反映您的代码设置的最后一个值,它等同于动画的最终状态。
Layer和View的关系
layer不能代替您的应用视图 – 也就是说,您无法仅基于layer对象创建可视界面。layer为view提供基础结构。具体而言,layer可以更轻松,更高效地绘制视图内容并进行动画处理,并在此过程中保持较高的帧率。但是,有很多layer没有做的事情:layer不处理事件、绘制内容、参与响应者链或做许多其他事情。因此,每个应用程序都必须有一个或多个视图来处理这些交互。
在iOS中,每个视图都由相应的layer对象支持,但在OS X中,您必须确定哪些视图应该具有layer。在OS X v10.8及更高版本中,向所有视图添加layer可能很有意义。但是,您不需要这样做,并且在开销不必要和不需要的情况下仍可以禁用layer。layer确实会增加应用程序的内存开销,但它们的好处往往超过了缺点,所以最好在禁用layer支持之前测试应用程序的性能。
当为view启用layer支持时,您将创建被称为layer支持的视图。在分层视图中,系统负责创建底层layer对象并使该layer与视图保持同步。所有iOS视图都是分层支持的,OS X中的大多数视图也是如此。但是,在OS X中,您还可以创建layer托管视图,该视图是您自己提供layer对象的视图。对于layer托管视图,AppKit采取了一种管理layer的方法,并且不会响应视图更改而对其进行修改。
注意:对于支持layer的view,建议尽可能操作view而不是layer。 在iOS中,view只是layer对象的封装,所以对layer进行的任何操作通常都可以正常工作。 但是在iOS和OS X的某些情况下,操纵layer而不是view可能不会产生所需的结果。 本文尽可能指出这些缺陷,并试图提供帮助您解决问题的方法。
除了与view关联的layer外,还可以创建没有相应视图的layer objects。 您可以将这些独立layer object嵌入到应用中的任何其他layer object中,包括与视图关联的layer object。 您通常使用独立的layer object作为特定优化路径的一部分。 例如,如果您想在多个位置使用相同的image,则可以加载image一次,并将其指定给多个独立layer object,然后将这些layer对象添加到layer tree中。 然后,每个layer都会引用源image,而不是尝试在内存中创建该image的副本。
设置layer objects / Setting Up Layer Objects
Layer object是Core Animation的核心。 图层管理您的应用的视觉内容,并提供修改该内容的风格和视觉外观的选项。 虽然iOS应用程序已自动启用图层支持,但OS X应用程序的开发人员必须先显示启用它,才能利用性能优势。 启用后,您需要了解如何配置和操作应用的图层以获得所需的效果。
在iOS中,每个view默认有自己的layer。OSX中,必须显示对NSView的layer进行设置。通过调用seWantsLayer方法。
改变Layer Object和View的关联
带有layer的views默认创建一个CALayer的实例,大部分情况你不必需要一个不同于CALayer的类型object。但是 Core Animation 提供了不同的layer class,每个class提供了不同的针对特性。选择不同的layer class可以让你有能力提高性能或者支持特定类型的内容。比如:CATiledLayer类对显示大图片进行了优化。
改变UIView的layer class
你可以改变iOS view使用的layer 类型,通过重写 view的layerClass方法、返回不同class。大部分iOS Views 创建CALayer 对象,使用它啦作为内容的后台存储。对大部分你的views,这个默认选项很好而且你不应该改变它。但是你可以发现不同的layer class 可能针对特定的情况。比如:你可以想要在以下情况改变layer class:
- 你的 view 使用 Metal 或者 OpenGL ES绘制内容,这种情况你应该使用CAMetalLayer 和 CAEAGLLayer
- 有一种更好的特殊layer,能提供更好的性能
- 你想要利用某些特定的Core Animation layer class,比如particle emitters 或者 replicator
通过以下方法修改layer class:
+ (Class) layerClass {
|
return [CAMetalLayer class];
|
}
|
更改NSView使用的图层类
您可以通过重写makeBackingLayer方法来更改NSView对象使用的默认图层类。 在实现此方法时,创建并返回您想要AppKit使用的layer objects来备份您的自定义视图。 在您想要使用自定义图层(如滚动或平铺图层)的情况下,您可以重写此方法。
OSX中可以使用 layer hosting(托管)改变 layer objec,
layer-hosting视图是您自己创建和管理基础layer objects的NSView对象。 您可以在要控制与视图关联的layer objects类型的情况下使用layer-hosting。 例如,您可以创建一个layer-hosting视图,以便您可以分配默认CALayer类以外的图层类。 您也可以在需要使用单个视图来管理独立层的层次结构的情况下使用它。
当你调用你的视图的setLayer:方法并提供一个layer objects时,AppKit会采取一种不干涉的方式来处理该图层。 通常,AppKit更新视图的layer objects,但在layer-hosting情况下,它不适用于大多数属性。
要创建layer-hosting视图,请在屏幕上显示视图之前创建layer objects并将其与视图相关联,如图所示。 除了设置layer objects之外,还必须调用setWantsLayer:方法来让视图知道它应该使用图层。
// Create myView…
|
[myView setWantsLayer:YES];
|
CATiledLayer* hostedLayer = [CATiledLayer layer];
|
[myView setLayer:hostedLayer];
|
// Add myView to a view hierarchy.
|
如果你选择自己托管 layer,必须设置 contentsScale属性,在合适时机提供一个高分辨率内容。
不同的Layer class提供了不同的行为
Core Animation定义了许多标准的layer class,每个类都是为特定用例而设计的。 CALayer类是所有layer object的root类。 它定义了所有layer object必须支持的行为,并且是层级视图所使用的默认类型。
如下表格:
Class |
Usage |
CAEmitterLayer | 用于实现基于Core Animation的粒子发射系统。发射者 layer object 控制着粒子的产生和起源 |
CAGradientLayer | 用于绘制填充形状的颜色渐变layer(在任何圆角范围内) |
CAMetalLayer | 用于设置和产生可绘制的纹理,用来绘制图层内容 |
CAEAGLLayer/CAOpenGLLayer | 用于OpenGL ES (iOS) or OpenGL (OS X). 中 设置 绘制layer内容的backing store存储和上下文 |
CAReplicatorLayer | 当你想要自动复制一个或者多个 sublayers。复制器replicator 负责为你复制,使用 指定的属性来改变拷贝的外观。 |
CAScrollLayer | 用于管理一个大的可滚动的、由多个sublayers组成的区域 |
CAShapeLayer | 用于绘制三次贝塞尔样式。shape layer 对绘制 基于 path 的 shapes特别有效,因为它们总是会形成一条crisp path,与绘制到图层后台存储区的路径相反,在缩放时看起来不太好。 但是,crisp results 在主线程上启动渲染形状并缓存结果。 |
CATextLayer | 用于绘制一个纯text或者一个attributed string。 |
CATiledLayer | 用于管理一个能分切成小片单元而且每片能独立的绘制、支持缩放的大图 |
CATransformLayer | 用于绘制一个真3d layer 层级,而不是由其他层类实现的扁平层层次结构。 |
QCCompositionLayer | 用于绘制Quartz Composer composition. (OS X only) |
提供 Layer 的内容
layer 是管理你的应用程序内容的数据对象。图层的内容由包含要显示的可视化数据的bitmap。可以通过以下三种方式之一提供该bitmap的内容:
- 直接将image object分配assign给layer object的content属性。 (适用于从未或很少改变的图层内容。)
- 将delegate object分配给layer,并让delegete 绘制图层的内容。 (最适合可能定期更改的图层内容,并且可以由外部对象提供,比如 view)
- 定义一个layer subclass并重写其中一个绘制方法以自行提供图层内容。 (如果必须创建自定义图层子类,或者要更改图层的基本绘制行为,使用此技术)
唯一需要担心为图层提供内容的时机是自己创建layer objects时。如果您的应用程序仅包含层次支持的视图,则不必担心使用任何前述技术来提供图层内容。图层支持的视图以尽可能高效的方式自动为其关联图层提供内容
使用 image 图片作为 layer 的内容
由于图层只是用于管理bitmap image的容器,因此可以将image 直接分配给图层的content属性。 为图层分配图像非常简单,您可以指定想要在屏幕上显示的确切图像。 该图层使用您直接提供的图像对象,并且不会尝试创建该图像的自己的副本。 如果您的应用在多个位置使用相同的图像,此行为可以节省内存。
您分配给图层的图像必须是CGImageRef类型。 (在OS X v10.6及更高版本中,您也可以分配NSImage对象。)分配图像时,请记住提供分辨率与本机设备分辨率相匹配的图像。 对于具有Retina显示器的设备,这可能还需要您调整图像的contentsScale属性
使用 Delegate 来未 layer 提供内容
如果你的layer 内容动态变化,你可以shying一个delegate object 来在需要的时候更新内容。显示时,layer 调用 代理的方法来提供内容:
如果你的代理实现了 displayLayer: 方法,那么它负责创建一个bitmap,并且分配这个bitmap给 layer 的content
如果你的代理实现了 drawLayer:inContext 方法,Core Animation 负责创建bitmap,创建一个graphics context 来绘制到一个bitmap,然后调用你的代理方法来填充bitmap。你所有的代理方法必须绘制到提供的这个graphics context中。
这个 delegate 对象只能实现displayLayer: 或者 drawLayer:inContext: 其中之一。如果实现了两个,layer 只会使用displayLayer:方法。
重写 displayLayer: 方法适应大部分情况。如下图,代理使用了一个helper object 来加载和显示需要的image。代理方法基于内部状态选择那个图片要显示。
– (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];
|
}
|
}
|
如果你没有预先绘制的图片,或者一个helper object 来创建bitmap,你的代理能使用 drawLayer:inContext:来动态绘制。下图显示了这个方法的实现。大力绘制了一个简单的曲线,填充了width和当前绘制颜色。
– (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的视图自动把自己作为layer的delegate,并实现所需的代理功能,并且您不应该改变这种配置。相反,您应该实现view的drawRect:方法。
在OS X v10.8及更高版本中,替代绘图的方法是通过覆盖视图的wantsUpdateLayer和updateLayer方法来提供bitmap。 重写wantsUpdateLayer并返回YES会导致NSView类遵循备用渲染路径。 视图不会调用drawRect:,而是调用updateLayer方法,该方法的实现必须将位图直接分配给图层的内容属性。 这是AppKit希望直接设置视图的图层对象内容的场景之一。
通过 SubClassing来提供layer 的内容
如果你正在实现自定义layer class,则可以覆盖layer class的绘制方法以执行任何绘制。layer class本身生成自定义内容的情况并不常见,但图层当然可以管理内容的显示。例如,CATiledLayer类通过将其分成可以单独管理和渲染的更小的图块来管理大图像。因为只有这个layer具有关于在任何给定时间需要渲染哪些图块的信息,所以它直接管理图形行为。
在继承子类时,可以使用以下任一技术来绘制图层的内容:
- 覆盖图层的display方法,并使用它直接设置图层的内容属性。
- 覆盖图层的drawInContext:方法并使用它绘制到提供的图形上下文中。
您重写的方法取决于您在绘图过程中需要多少控制。display方法是更新图层内容的主要入口点,因此覆盖该方法将使您完全控制该过程。覆盖display方法也意味着你负责创建要分配给content属性的CGImageRef。如果您只想绘制内容(或者让图层管理绘图操作),则可以改为重写drawInContext:方法,并让图层为您创建后备存储。
调整你提供的内容
当你给layer的contents属性分配了一个image,layer的contentsGravity 属性决定这个图片如何被操作来适应当前范围。默认的,如果一个image大/小于当前范围,layer object 缩放image 来适应。如果layer范围的比例和image的比例不同,将会导致image被拉伸。你可以使用contentsGravity来确定你的内容在最佳效果呈现。
你可以指定给contentsGravity属性的值分以下两种类别:
- 基于位置的gravity 常量,让你 把你的image钉到layer 范围的一个特定的边或者角落,而不会缩放它。
- 基于缩放的gravity 常量,让你使用一个/几个选项来拉伸你的image,某些选项预置了比例,有的没有。
下图显示了position-based gravity 设置如何影响你的image。除kCAGravityCenter常量外,每个常量都会将图像固定到图层边界矩形的特定边或角点。 kCAGravityCenter 将图层置于图层的中心。 这些常量都不会导致图像以任何方式缩放,因此图像总是以原始大小呈现。 如果图像大于图层边界,则可能会导致图像部分被裁剪,如果图像较小,图层未覆盖的图层部分会显示图层的背景色(如果已设置)。
下图显示了 scaling-based gravity 常量如何影响你的image。如果这些图片不正好在layer的范围内的话,所有这些常量都会缩放图片。这些模式的不同之处是他们如何处理image的原始比例。某些模式预处理比例。默认的,一个layer的contentsGravity属性值是kCAGravityResize——唯一一个不预处理比例的模式。
处理高分辨率图片
layer对于屏幕分辨了know nothing。一个 layer 简单的存储了 bitmap的指针和在给定可用像素内显示它的最佳方式。如果你关联一个image到layer的content属性,你必须用layer的contentsScale属性来告诉 Core Animation 关于image的分辨率。默认值是1.0, 让图片显示在标准分辨率下。如果你的图片要显示在Retina 下,设置值为2.0
改变contentsScale值只针对你直接关联一个bitmap到layer。UIKit和AppKit 的 layer-backed view基于屏幕分辨率给自己自动设置缩放值。例如,如果你在OSX上关联一个NSImage对象到contents属性,Appkit 会查找image是否有 标准和高分辨率变量。如果有,appkit 使用合适的变量。并且设置合适的contentsScale。
OSX中,position-based gravity 常量影响从分配给图层的NSImage对象中选择图像呈现的方式。 由于这些常量不会导致缩放图像,因此Core Animation依赖contentsScale属性来选择具有最适合像素密度的图像表示。
在OS X中,layer的委托可以实现:shouldInheritContentsScale:fromWindow:method并使用它来响应比例的变化。 只要给定窗口的分辨率发生变化,AppKit就会自动调用该方法,这可能是因为窗口在标准分辨率和高分辨率屏幕之间移动。 如果呈现支持更改layer图像的分辨率,那么实现此方法应返回YES。 该方法应该根据需要在新分辨率下更新图层的内容。
调整一个layer的视觉风格和外观
layer object 有border,background颜色等内置的视觉装饰,你可以用来提供给layer的主要内容。因为这些视觉装饰不需要你做任何绘制工作,他们让某些情况中单独使用layer成为可能。所有你要做的是设置一个属性,layer 处理必要的绘制,包括任何动画。
Layer有自己的background和border。一个layer能显示一个填充的背景、基于图片画border。背景色在layer内容图后(behind)绘制,border 在image上(top)绘制,如下图,如果layer 包含sublayers,他们也在border下显示。因为background color 在image后方,颜色会覆盖穿透任何image的透明部分。
以下代码显示颜色和border的设置:
myLayer.backgroundColor = [NSColor greenColor].CGColor;
|
myLayer.borderColor = [NSColor blackColor].CGColor;
|
myLayer.borderWidth = 3.0;
|
注意:你可以使用任何类型的color来设置一个layer的background color,包括透明和使用图案图片。当使用图案的时候,小心core graphic负责处理图案的绘制,不使用标准的坐标系统。这个坐标系和iOS默认坐标系不同。同样,iOS上的图片绘制默认会反向,除非你反转坐标系。
如果你设置 layer 背景色 为一个不透明色,注意设置layer的 opaque 属性为 YES。这样做可以在组合layer到屏幕时提升性能,爬出不需要的layer 存储管理的alpha 通道。不过,如果一个层也有一个非零的圆角,你就不能把它标记为不透明层。
Layer支持圆角。你可以创建圆角矩形到一个layer,通过添加一个圆角半径。一个corner radius时一个视觉装饰,遮盖部分角落。如图,因为它启动了一个透明蒙版,圆角不会影响layer content属性的图片,除非你设置 masksToBounds 为YES。可是,圆角总是影响layer的背景色和baorder。
使用 cornerRadius属性设置一个layer的圆角。
Layer支持内置Shadow。CALayer类包含许多属性来配置 shadow 效果。一个shadow 给 layer添加深度,让他看起来浮在内容上。默认情况下,图层阴影的不透明度值设置为0,这有效隐藏了阴影。 将不透明度更改为非零值会导致Core Animation绘制阴影。 由于默认情况下阴影直接位于图层下方,因此您可能还需要更改阴影的偏移量,然后才能看到阴影。 不过,需要记住的是,您为阴影指定的偏移量是使用图层的本地坐标系来应用的,这在iOS和OS X上是不同的。下图显示了一个阴影向下延伸到 图层右侧。 在iOS中,这需要为y轴指定正值,但在OS X中,该值需要为负值。
向图层添加阴影时,阴影是图层内容的一部分,但实际上是延伸到图层的边界矩形之外。 因此,如果为该图层启用了masksToBounds属性,则阴影效果会在边缘被剪切。 如果图层包含任何透明内容,则可能会导致一种奇怪的效果,即直接位于图层下方的阴影部分仍然可见,但延伸到图层之外的部分不会。 如果你想要一个阴影,但也想使用边界遮罩,你可以使用两层而不是一层。 将蒙版应用于包含内容的图层,然后将该图层嵌入到启用阴影效果的大小完全相同的第二个图层中。
通过filter将视觉效果添加到OS X的视图中
在OS X应用程序中,您可以将Core Image filter直接应用到图层的内容。 您可能会这样做,以模糊或锐化图层的内容、更改颜色、扭曲内容或执行许多其他类型的操作。 例如,图像处理程序可能会使用这些滤镜非破坏性地修改图像,而视频编辑程序可能会使用它们来实现不同类型的视频转换效果。 而且由于滤镜应用于硬件中的图层内容,因此渲染速度很快且平滑。
对于给定图层,可以将滤镜应用于图层的前景和背景内容。 前景内容由图层本身包含的所有内容组成,包括图像的内容属性、背景颜色、边框和子图层的内容。 背景内容是直接在图层下方的内容,但实际上并不是图层本身的一部分。 大多数图层的背景内容是其直接super layer的内容,可能完全或部分被图层遮挡。 例如,当您希望用户专注于图层的前景内容时,您可以将模糊滤镜应用于背景内容。
您可以通过将CIFilter对象添加到图层的以下属性来指定滤镜:
- filters属性包含一个只影响图层前景内容的filter数组。
- backgroundFilters属性包含仅影响图层背景内容的filter数组。
- compositingFilter属性定义图层的前景和背景内容如何合成在一起。
要为图层添加filter,您必须先找到并创建CIFilter对象,然后在将其添加到图层之前对其进行配置。 CIFilter类包含几个用于查找可用Core image filter的类方法,例如filterWithName:,不过,创建filter只是第一步。许多滤镜具有定义滤镜如何修改图像的参数。例如,框模糊滤镜具有影响所应用的模糊量的输入半径参数。您应始终为这些参数提供值作为filter配置过程的一部分。但是,您不需要指定的一个常见参数是由图层本身提供的输入图像。
将滤镜添加到图层时,最好在将滤镜添加到图层之前配置滤镜参数。这样做的主要原因是,一旦添加到图层,您无法修改CIFilter对象本身。但是,您可以使用图层的setValue:forKeyPath:方法在事实之后更改filter值。
下图显示了如何创建并向图层对象应用捏合失真filter。此滤镜将内层的源像素向内捏住,使距离指定中心点最近的那些像素变形。请注意,在示例中,您不需要为filter指定输入图像,因为图层的图像是自动使用的。
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:250.0 Y:150.0] forKey:@”inputCenter”];
|
myLayer.filters = [NSArray arrayWithObject:aFilter];
|
OSX的layer重绘机制会影响性能
在OS X中,支持层的视图支持几种不同的策略,以确定何时更新底层的内容。 由于本机AppKit绘图模型与Core Animation引入的绘图模型之间存在差异,因此这些策略可以更轻松地将旧代码迁移到Core Animation。 您可以在逐个视图的基础上配置这些策略,以确保每个视图的最佳性能。
每个视图定义一个layerContentsRedrawPolicy方法,该方法返回视图图层的重绘策略。 您可以使用setLayerContentsRedrawPolicy:方法设置策略。 为了保持与传统绘图模型的兼容性,AppKit默认将重绘策略设置为NSViewLayerContentsRedrawDuringViewResize。 但是,您可以将策略更改为表2-2中的任何值。 请注意,建议的重绘策略不是默认策略。
Policy | Usage |
NSViewLayerContentsRedrawOnSetNeedsDisplay |
这是推荐的策略。 使用此策略,视图几何体更改不会自动使视图更新其图层的内容。 相反,该图层的现有内容被拉伸和操纵以促进几何变化。 要强制视图重绘自己并更新图层的内容,您必须显式调用视图的setNeedsDisplay:方法。
此策略最接近地表示Core Animation层的标准行为。 但是,它不是默认策略,必须明确设置。
|
NSViewLayerContentsRedrawDuringViewResize |
这是默认的重绘策略。此策略通过在视图的几何更改时回顾图层的内容来保持与传统AppKit绘图的最大兼容性。这种行为导致视图的drawRect:方法在调整大小操作期间在应用程序主线程上多次调用。
|
NSViewLayerContentsRedrawBeforeViewResize |
通过这个策略,AppKit在任何调整大小操作并缓存该位图之前将其绘制为最终大小。调整大小操作使用缓存的位图作为起始图像,将其缩放以适合旧的边界矩形。然后将位图动画化为其最终大小。这种行为可能导致视图的内容在动画开始时出现拉伸或扭曲,并且在初始外观不重要或不明显的情况下更好。
|
NSViewLayerContentsRedrawNever |
有了这个策略,即使调用setNeedsDisplay:方法,AppKit也不会更新图层。 此策略对于内容不会更改以及视图大小偶尔更改的视图来说最适合。 例如,您可以将此用于显示固定大小内容或背景元素的视图。
|
查看重绘策略可以减少使用独立子图层来提高绘图性能的需求。 在引入视图重绘策略之前,有一些layers支持的视图比需要的更频繁地绘制,从而导致性能问题。 这些性能问题的解决方案是使用子图层呈现视图内容的那些不需要定期重绘的部分。 通过在OS X v10.6中引入重绘策略,现在建议您将图层支持的视图的重绘策略设置为适当的值,而不是创建显式的子图层次结构。
给layer添加自定一个属性
CAAnimation和CALayer类扩展了kvc以支持自定义属性。 您可以使用此行为将数据添加到图层,并使用您定义的自定义key检索它。 您甚至可以将操作与您的自定义属性相关联,以便在更改属性时执行相应的动画。
输出一个基于layer的view
在输出过程中,图层会根据需要重新绘制其内容以适应输出环境。 Core Animation在渲染到屏幕时通常依赖缓存的bitmap,而在输出时会重新绘制该内容。 特别是,如果一个图层背景视图使用drawRect:方法提供图层内容,Core Animation将在输出期间再次调用drawRect:以生成输出图层内容。
动态化 Layer 的内容
Core Animation提供的基础架构可以轻松创建应用layer的复杂动画,并扩展到拥有这些图层的任何视图。 示例包括更改图层框架矩形的大小,更改其在屏幕上的位置,应用旋转变换或更改其不透明度。 使用Core Animation,启动动画通常就像更改属性一样简单,但您也可以创建动画并明确设置动画参数。
动态化简单的变化到layer的属性上
根据需要可以隐式或者显示的执行简单的动画。隐式动画使用默认的timing和动画属性执行动画,然而显示动画需要你自己配置一个animation 对象。所以对于简单的动画,隐式动画是一个好的选择。
隐式动画包含layer的属性的改变,让Core Animation处理动态化过程。layer定义了许多影响图层可见外观的属性。 改变这些属性之一是动画改变外观的一种方法。 例如,将图层的不透明度从1.0更改为0.0会导致图层淡出并变为透明。
重要:尽管你有时可以直接使用Core Animation界面对layer-based view进行动画制作,但这样做往往需要额外的步骤。
要触发隐式动画,您只需更新layer object的属性即可。 在修改layer tree中的图层对象时,这些对象会立即反映您的更改。 但是,图层对象的视觉外观不会立即改变。 相反,Core Animation会使用您的更改作为t来创建trigger,调度一个或多个隐式动画以供执行。 像下图中那样进行更改会导致Core Animation为您创建一个动画对象,并安排该动画从下一个更新周期开始运行:
theLayer.opacity = 0.0;
|
要使用animation object显式进行相同的更改,创建一个CABasicAnimation对象并使用该对象来配置动画参数。 在将动画添加到图层之前,您可以设置动画的开始和结束值,更改持续时间或更改任何其他动画参数。 下图显示了如何使用动画对象淡出图层。 创建对象时,需要为要设置动画的属性指定关键路径,然后设置动画参数。 要执行动画,可以使用addAnimation:forKey:方法将其添加到要进行动画处理的图层中。
CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:@”opacity”];
|
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
|
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
|
fadeAnim.duration = 1.0;
|
[theLayer addAnimation:fadeAnim forKey:@”opacity”];
|
// Change the actual data value in the layer to the final value.
|
theLayer.opacity = 0.0;
|
提示:创建显式动画时,建议始终将值分配给动画对象的fromValue属性。 如果您未为此属性指定值,则Core动画将该图层的当前值用作起始值。 如果您已经将该属性更新为其最终值,那可能不会产生您想要的结果。
与更新图层对象的数据值的隐式动画不同,显式动画不会修改layer tree中的数据。显式动画只会产生动画。在动画结束时,Core Animation从该layer中移除该animation object,并使用其当前数据值重绘该图层。如果您希望显式动画的更改是永久性的,则还必须更新图层的属性,如上例所示。
隐式和显式动画通常在当前run loop结束后开始执行,并且当前线程必须具有run loop才能执行动画。如果更改多个属性,或者将多个动画对象添加到图层,则所有这些属性更改都会同时进行动画。例如,您可以通过同时配置两个动画来淡入淡出图层。但是,您也可以将动画对象配置为在特定时间开始。
使用关键帧动画改变layer属性 / Using a Keyframe Animation to Change Layer Properties
尽管基于属性的动画将属性从开始值更改为结束值,但CAKeyframeAnimation对象允许您通过一组可以或者不可以以线性的方式设置目标值的动画。 Keyframe Animation 由一组目标数据值和应达到每个值的时间组成。 在最简单的配置中,您可以使用数组指定值和时间。 对于图层位置的更改,也可以按照path进行更改。 animation object采用你指定的Keyframe,并通过在给定时间段内从一个值插入到下一个值来构建动画。
下图显示了一个5秒的layer position 动画,position 沿着一个path 运动,path使用的是CGPathRef类型。
下面是代码:
// create a CGPath that implements two arcs (a bounce)
|
CGMutablePathRef thePath = CGPathCreateMutable();
|
CGPathMoveToPoint(thePath,NULL,74.0,74.0);
|
CGPathAddCurveToPoint(thePath,NULL,74.0,500.0,
|
320.0,500.0,
|
320.0,74.0);
|
CGPathAddCurveToPoint(thePath,NULL,320.0,500.0,
|
566.0,500.0,
|
566.0,74.0);
|
CAKeyframeAnimation * theAnimation;
|
// Create the animation object, specifying the position property as the key path.
|
theAnimation=[CAKeyframeAnimation animationWithKeyPath:@”position”];
|
theAnimation.path=thePath;
|
theAnimation.duration=5.0;
|
// Add the animation to the layer.
|
[theLayer addAnimation:theAnimation forKey:@”position”];
|
设置 keyframe 值
关键帧的值是keyframe动画的最重要部分。这个值定义了动画执行期的行为。主要的设置方式是一个比如包含CGPoint数据类型(比如layer的anchorPoint锚点和position 属性)的对象数组,当然CGPoint也可以变成CGPathRef。
当指定数组数据时,你往array里添加的数据依赖于你需要的属性的数据类型。你能直接添加某种对象到数组,或者,某些必须背转换为id类型的对象,所有 类型和结构都需要转成对象object,比如:
- CGRect,转为NSValue
- layer的transform 属性,需要吧CATransform3D转为NSValue。动态化次属性导致keyframe animation在每个layer上应用transform matrix
- borderColor 属性,转换每个CGColorRef为id类型
- CGFloat ,转为NSNumber
- layer的contents属性,指定一个CGImageRef的数组
对于接受CGPoint属性的属性,你可以创建一个转换为NSValue的point数组,或者你可以输用CGPathRef对象指定接下来的path。当你指定一个points数组时,keyframe animation对象绘制一条这些点之间的直线。当指定CGPathRef对象时,动画开始于path的开始点,沿着path轮廓执行,包括任何的曲线。开放和闭合的path都支持。
指定keyframe animation的时机
keyframe animation的时机timing和速率pacing比basic animations复杂很多,你可以用以下属性控制:
- calculationMode属性,定义动画time计算的法则。属性值影响其他timing相关属性的使用
-
- 线性 linear 和 cubic animations,指当 calculationMode属性设置为 kCAAnimationLinear or kCAAnimationCubic 时。意味着使用提供的时机信息来产生动画。这俩属性能让你对animation的time有最大化的控制权。
- Paced Animations,指calculationMode设置为kCAAnimationPaced or kCAAnimationCubicPaced 时。意味着不需要依赖于keyTimes或者timingFunctions提供的timing 值,而是,隐式的计算此值,匀速执行动画。
- Discrete animations,当calculationMode property 设置为 kCAAnimationDiscrete 时,会让属性从一个keyframe不被打断的跳到另一个keyframe。这个mode使用 keyTimes 属性的值,但是忽略 timingFunctions 属性。
- KeyTimes属性,指定 time maker 在何时应用每个keyframe 的值。这个属性只有当 calculationMode设置为 kCAAnimationLinear or kCAAnimationCubic或者 kCAAnimationDiscrete时有效
- timingFunctions属性,指定每个keyframe segment的时间曲线。
如果你想自己处理animation的timing,使用kCAAnimationLinear or kCAAnimationCubic mode和 keyTimes and timingFunctions属性。 keyTimes定义了应用每个关键帧值的时间点。所有中间值intermediate values 的时间由 timingFunctions控制,这允许您将ease-in or ease-out曲线应用到每个segment。如果您没有指定任何timingFunctions,则timing是线性的。
停止一个正在运行的显示动画
可以停止一个显示动画:
- 移除一个layer的单独的动画对象,调用 layer的 removeAnimationForKey 方法。这个方法使用的key时 addAnimation:forkey里的key。不能为nil。
- 移除layer的所有动画对象,调用 removeAllAnimations方法。该方法移除所有正在进行的动画,用当前状态信息重回layer。
注意:你不能直接从一个layer移除隐式动画。
当你移除一个layer的动画是,Core Animation 会用当前值重绘layer。因为当前值进场是animation的结束值,可能造成layer跳跃显示。如果你想要layer 保持动画上一贞的状态,你可以使用 presentation tree里的对象来处理结果值,把他们设置到layer tree。
同时动态化多个改变
如果你想要同时应用多个动画到一个layer 对象,你可以 使用CAAnimationGroup来组合他们。使用group 对象 提供了这些object的单独配置点。timing和duration 的值同时应用到组里所有的动画对象。如下图:
// Animation 1
|
CAKeyframeAnimation* widthAnim = [CAKeyframeAnimation animationWithKeyPath:@”borderWidth”];
|
NSArray* widthValues = [NSArray arrayWithObjects:@1.0, @10.0, @5.0, @30.0, @0.5, @15.0, @2.0, @50.0, @0.0, nil];
|
widthAnim.values = widthValues;
|
widthAnim.calculationMode = kCAAnimationPaced;
|
// Animation 2
|
CAKeyframeAnimation* colorAnim = [CAKeyframeAnimation animationWithKeyPath:@”borderColor”];
|
NSArray* colorValues = [NSArray arrayWithObjects:(id)[UIColor greenColor].CGColor,
|
(id)[UIColor redColor].CGColor, (id)[UIColor blueColor].CGColor, nil];
|
colorAnim.values = colorValues;
|
colorAnim.calculationMode = kCAAnimationPaced;
|
// Animation group
|
CAAnimationGroup* group = [CAAnimationGroup animation];
|
group.animations = [NSArray arrayWithObjects:colorAnim, widthAnim, nil];
|
group.duration = 5.0;
|
[myLayer addAnimation:group forKey:@”BorderChanges”];
|
一个组织动画的更高级的方式是使用事物对象 transaction object。transaction 提供更灵活的处理方式,让你创建嵌套的动画组,分别分配不同动画参数。
检测动画的结束
Core Animation 提供了对animation开始和结束状态检测的支持。这些通知是做清理工作的好时机。比如,你可能使用一个start notification 来设置相关状态信息,使用结束通知来停止这个状态。两种方式通知:
- 添加一个completion block,通过设置 setCompletionBlock:方法。当所有animation事务停止时,事务执行这个block。
- 关联一个代理delegate到CAAnimation,实现 animationDidStart和animationDidEnd方法
如果你想要链接两个动画,一个结束另一个开始,不要用这种通知的方式。你应该使用beginTime属性来启动在每个想要的时间启动动画。
如何动态化 Layer-backed Views
如果一个layer时隶属于 layer-backed view的,创建动画的推荐方式是使用 view-based 的动画,该动画通过UIKit和AppKit提供。
iOS的Layer修改规则
因为iOSViews始终有一个底层的layer,UIView 类从自己获取大量layer数据。所以,你对layer的改变是自动反应给视图对象的。这个行为意味着你可以使用Core Animation和UIView接口作出变化。
如果你想要使用Core Animation来初始化动画,你必须从基于视图的动画block中发出所有的Core Animation调用。所以所有你在block外的变化都不会动态化。如下图,展示了如何隐式改变layer的透明度、显示改变他的位置。这个例子中,myNewPosition 变量在block前被计算。两个动画同时开始,但是透明度度化用的默认timing,position动画使用了指定的时间。
[UIView animateWithDuration:1.0 animations:^{
|
// Change the opacity implicitly.
|
myView.layer.opacity = 0.0;
|
// Change the position explicitly.
|
CABasicAnimation* theAnim = [CABasicAnimation animationWithKeyPath:@”position”];
|
theAnim.fromValue = [NSValue valueWithCGPoint:myView.layer.position];
|
theAnim.toValue = [NSValue valueWithCGPoint:myNewPosition];
|
theAnim.duration = 3.0;
|
[myView.layer addAnimation:theAnim forKey:@”AnimateFrame”];
|
}];
|
修改 OS X layer的规则
要在OS X中动画化对层次视图的更改,最好使用视图本身的界面。 如果有的话,您应该很少直接修改附加到您的某个图层支持的NSView对象的图层。 AppKit负责创建和配置这些图层对象并在您的应用运行时管理它们。 修改图层可能会导致图层与视图对象不同步,并可能导致意外的结果。 对于层次支持的视图,您的代码绝对不能修改图层对象的任何以下属性:
- anchorPoint
- bounds
- compositingFilter
- filters
- frame
- geometryFlipped
- hidden
- position
- shadowColor
- shadowOffset
- shadowOpacity
- shadowRadius
- transform
重要提示:上述限制不适用于图层托管视图。如果您创建了图层对象并手动将其与视图关联,则需要修改该图层的属性并保持相应视图对象的同步。
默认情况下,AppKit为其layer-backed的视图禁用隐式动画。视图的animator proxy object为您自动重新启用隐式动画。如果您想直接为图层属性设置动画,则还可以通过将当前NSAnimationContext对象的allowsImplicitAnimation属性更改为YES来以编程方式重新启用隐式动画。再次,您应该只对不在上述列表中的动画属性执行此操作。
记得把更新View的约束作为动画的一部分
如果你使用了 基于约束的布局规则来管理view的位置,配置动画时,你必须删除可能和动画有关的约束。约束影响view的位置或者大小的任何变化。他们也影响视图和子视图的关系。如果你动态化改变了他们,你可以移除约束,修改大小/位置,然后在把需要的新约束加回来。
核心动画编程指南第一部分,完