本文翻译整理自:Core Animation Programming Guide(
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004514
文章目录
- 一、关于核心动画
- 1、概览
- 核心动画管理你的应用内容
- 图层修改触发动画
- 图层可以组织成层次结构
- 操作可让您更改图层的默认行为
- 2、如何使用本文档
- 3、先决条件
- 4、也可以看看
- 二、核心动画基础
- 1、图层为绘图和动画提供了基础
- 基于图层的绘制模型
- 基于图层的动画
- 2、图层对象定义自己的几何形状
- 图层使用两种类型的坐标系
- 锚点影响几何操作
- 可以在三维空间中操纵图层
- 3、图层树反映动画状态的不同方面
- 4、层和视图之间的关系
- 三、设置图层对象
- 1、在你的应用中启用核心动画支持
- 2、更改与视图关联的图层对象
- 更改 UIView 使用的 Layer 类
- 更改 NSView 使用的 Layer 类
- 层托管可让您在 OS X 中更改层对象
- 不同的图层类别提供专门的行为
- 3、提供图层的内容
- 使用图像作为图层的内容
- 使用代理提供图层的内容
- 通过子类化提供图层内容
- 调整您提供的内容
- 使用高分辨率图像
- 4、调整图层的视觉样式和外观
- 图层有自己的背景和边框
- 图层支持圆角
- 图层支持内置阴影
- 过滤器为 OS X 视图添加视觉效果
- 5、OS X 视图的图层重绘策略影响性能
- 6、向层添加自定义属性
- 7、打印基于图层的视图的内容
- 四、动画图层内容
- 1、为图层属性的简单变化制作动画
- 2、使用关键帧动画更改图层属性
- 指定关键帧值
- 指定关键帧动画的时间
- 3、停止正在运行的显式动画
- 4、同时为多个变化添加动画效果
- 5、检测动画的结束
- 6、如何为基于图层的视图制作动画
- iOS 中修改图层的规则
- 在 OS X 中修改图层的规则
- 记得在动画过程中更新视图约束
- 五、构建层级结构
- 1、将图层排列成图层层次结构
- 添加、插入和删除子层
- 子图层的定位和大小
- 图层层次如何影响动画
- 2、调整图层层次的布局
- 在 OS X 中使用约束来管理图层层次结构
- 为你的 OS X 图层层次设置自动调整规则
- 手动布局图层层次
- 3、子图层和裁剪
- 4、在图层之间转换坐标值
- 六、高级动画技巧
- 1、过渡动画支持图层可见性的更改
- 2、自定义动画的时间
- 3、暂停和恢复动画
- 4、显式事务让您可以更改动画参数
- 5、为动画添加透视效果
- 七、改变图层的默认行为
- 1、自定义动作对象采用 CAAction 协议
- 2、操作对象必须安装在图层上才能产生效果
- 3、使用 CATransaction 类暂时禁用操作
- 八、提高动画性能
- 1、为你的 OS X 视图选择最佳的重绘策略
- 2、更新 OS X 中的图层以优化渲染路径
- 3、常规提示和技巧
- 尽可能使用不透明层
- 对 CAShapeLayer 对象使用更简单的路径
- 为相同图层明确设置图层内容
- 始终将图层大小设置为整数值
- 根据需要使用异步图层渲染
- 向图层添加阴影时指定阴影路径
- 九、图层样式属性动画
- 1、几何特性
- 2、背景属性
- 3、图层内容
- 4、子层内容
- 5、边框属性
- 6、过滤器属性
- 7、阴影属性
- 8、不透明度属性
- 9、蒙版属性
- 十、可动画的属性
- 1、CALayer 动画属性
- 2、CIFilter 动画属性
- 十一、键值编码扩展
- 1、符合键值编码的容器类
- 2、默认值支持
- 3、包装约定
- 4、结构的关键路径支持
- CATransform3D 关键路径
- CGPoint 关键路径
- CGSize 关键路径
- CGRect 关键路径
一、关于核心动画
Core Animation 是 iOS 和 OS X 上可用的图形渲染和动画基础结构,可用于为应用程序的视图和其他可视元素制作动画。
使用 Core Animation,绘制动画每一帧所需的大部分工作都已为您完成。
您所要做的就是配置一些动画参数(例如起点和终点)并告诉 Core Animation 开始。
Core Animation 会完成其余工作,将大部分实际绘制工作交给板载图形硬件以加速渲染。
这种自动图形加速可实现高帧率和流畅的动画,而不会增加 CPU 负担并降低应用程序速度。
如果您正在编写 iOS 应用,那么无论您是否知道,您都在使用 Core Animation。
如果您正在编写 OS X 应用,那么您可以轻松使用 Core Animation。
Core Animation 位于 AppKit 和 UIKit 之下,与 Cocoa 和 Cocoa Touch 的视图工作流紧密集成。
当然,Core Animation 还具有扩展应用视图所公开的功能的接口,并让您可以更精细地控制应用的动画。
1、概览
您可能永远不需要直接使用 Core Animation,但是当您使用时,您应该了解 Core Animation 作为应用程序基础架构的一部分所扮演的角色。
核心动画管理你的应用内容
Core Animation 本身并不是一个绘图系统。
它是一个在硬件中合成和操作应用内容的基础结构。
这个基础结构的核心是层对象,您可以使用它们来管理和操作内容。
层将您的内容捕获到位图中,图形硬件可以轻松操作该位图。
在大多数应用中,层用于管理视图内容,但您也可以根据需要创建独立的层。
相关章节: 核心动画基础、设置图层对象、
图层修改触发动画
使用 Core Animation 创建的大多数动画都涉及修改图层的属性。
与视图一样,图层对象具有边界矩形、屏幕上的位置、不透明度、变换以及许多其他可修改的视觉导向属性。
对于大多数这些属性,更改属性的值会导致创建隐式动画,从而使图层从旧值动画到新值。
如果您希望更好地控制生成的动画行为,也可以显式地为这些属性设置动画。
相关章节: 动画图层内容、高级动画技巧、图层样式属性动画、可动画属性
图层可以组织成层次结构
图层可以按层次结构排列,以创建父子关系。
图层的排列方式会影响它们管理的视觉内容,其方式与视图类似。
附加到视图的一组图层的层次结构反映了相应的视图层次结构。
您还可以将独立图层添加到图层层次结构中,以扩展应用的视觉内容,而不仅仅是视图。
相关章节: 构建层级结构
操作可让您更改图层的默认行为
隐式图层动画是使用操作对象实现的,操作对象是实现预定义接口的通用对象。
Core Animation 使用操作对象来实现通常与图层关联的默认动画集。
您可以创建自己的操作对象来实现自定义动画,也可以使用它们来实现其他类型的行为。
然后,您可以将操作对象分配给图层的某个属性。
当该属性发生变化时,Core Animation 将检索您的操作对象并告诉它执行其操作。
相关章节: 更改图层的默认行为
2、如何使用本文档
本文档适用于 需要更好地控制应用动画 或希望使用图层 来提高应用绘制性能的开发者。
本文档还提供了有关 iOS 和 OS X 中图层与视图之间的集成的信息。
iOS 和 OS X 上的图层与视图之间的集成有所不同,了解这些差异对于创建高效的动画非常重要。
3、先决条件
您应该已经了解目标平台的视图架构,并熟悉如何创建基于视图的动画。
如果没有,您应该阅读以下文档之一:
- 对于 iOS 应用程序,您应该了解《iOS 视图编程指南》 中描述的视图架构。
- 对于 OS X 应用程序,您应该了解视图编程指南 中描述的视图架构。
4、也可以看看
有关如何使用 Core Animation 实现特定类型动画的示例,请参阅 Core Animation Cookbook。
二、核心动画基础
Core Animation 提供了一个通用系统,用于为应用的视图和其他可视元素制作动画。
Core Animation 并不是应用视图的替代品。
相反,它是一种与视图集成的技术,旨在为内容制作动画提供更好的性能和支持。
它通过将视图内容缓存到位图中来实现此行为,这些位图可由图形硬件直接操作。
在某些情况下,这种缓存行为可能需要您重新考虑如何呈现和管理应用的内容,但大多数时候您使用 Core Animation 时并不知道它的存在。
除了缓存视图内容之外,Core Animation 还定义了一种指定任意可视内容、将该内容与视图集成以及将其与其他所有内容一起制作动画的方法。
您可以使用 Core Animation 为应用视图和可视对象的更改制作动画。
大多数更改都与修改可视对象的属性有关。
例如,您可以使用 Core Animation 为视图的位置、大小或不透明度的更改制作动画。
当您进行此类更改时,Core Animation 会在属性的当前值和您指定的新值之间制作动画。
您通常不会使用 Core Animation 每秒 60 次替换视图的内容(例如在动画片中)。
相反,您可以使用 Core Animation 在屏幕上移动视图的内容、淡入或淡出该内容、对视图应用任意图形转换或更改视图的其他可视属性。
1、图层为绘图和动画提供了基础
图层对象是 3D 空间中组织的 2D 表面,是您使用 Core Animation 执行的所有操作的核心。
与视图一样,图层管理有关其表面的几何形状、内容和视觉属性的信息。
与视图不同,图层不定义自己的外观。
图层仅管理位图周围的状态信息。
位图本身可以是视图绘制自身或您指定的固定图像的结果。
因此,您在应用中使用的主要图层被视为模型对象,因为它们主要管理数据。
记住这个概念很重要,因为它会影响动画的行为。
基于图层的绘制模型
大多数图层并不在应用中进行任何实际的绘制。
相反,图层会捕获应用提供的内容并将其缓存在位图中,位图有时也称为后备存储*。
*随后,当您更改图层的属性时,您所做的只是更改与图层对象相关联的状态信息。
当更改触发动画时,Core Animation 会将图层的位图和状态信息传递给图形硬件,图形硬件使用新信息完成渲染位图的工作,如图1-1所示。
与软件相比,在硬件中操作位图可以产生更快的动画。
图 1-1 Core Animation 如何绘制内容
由于基于图层的绘制操作的是静态位图,因此它与更传统的基于视图的绘制技术有很大不同。
对于基于视图的绘制,对视图本身的更改通常会导致调用视图的drawRect:
方法以使用新参数重新绘制内容。
但这种方式的绘制成本很高,因为它是在主线程上使用 CPU 完成的。
Core Animation 尽可能通过操纵硬件中的缓存位图来实现相同或类似的效果,从而避免这种成本。
尽管 Core Animation 尽可能多地使用缓存内容,但您的应用仍必须提供初始内容并时常更新它。
您的应用有多种方法可以为图层对象提供内容,这些方法在提供图层的内容中有详细介绍。
基于图层的动画
图层对象的数据和状态信息与该图层内容在屏幕上的视觉呈现是分离的。
这种分离使 Core Animation 能够进行自我干预,并为从旧状态值到新状态值的变化制作动画。
例如,更改图层的位置属性会导致 Core Animation 将图层从其当前位置移动到新指定的位置。
对其他属性的类似更改会导致相应的动画。
图 1-2说明了您可以在图层上执行的几种动画类型。
有关触发动画的图层属性列表,请参阅 可动画属性。
图 1-2 可以在图层上执行的动画示例
在动画过程中,Core Animation 会在硬件中为您完成所有逐帧绘制。
您只需指定动画的起点和终点,Core Animation 便会完成其余工作。
您还可以根据需要指定自定义计时信息和动画参数;但是,如果您没有指定,Core Animation 会提供合适的默认值。
有关如何启动动画和配置动画参数的更多信息,请参阅 动画图层内容。
2、图层对象定义自己的几何形状
图层的职责之一是管理其内容的视觉几何形状。
视觉几何形状包括有关内容的边界、其在屏幕上的位置以及图层是否以任何方式被旋转、缩放或变换的信息。
与视图一样,图层具有框架和边界矩形,您可以使用它们来定位图层及其内容。
图层还具有视图所没有的其他属性,例如锚点,它定义了操作发生的点。
指定图层几何形状某些方面的方式也与为视图指定该信息的方式不同。
图层使用两种类型的坐标系
图层使用基于点的坐标系和单位坐标系来指定内容的位置。
使用哪种坐标系取决于所传达的信息类型。
当指定直接映射到屏幕坐标或必须相对于另一个图层指定的值(例如图层的position
属性)时,使用基于点的坐标。
当值不应与屏幕坐标相关联,因为它相对于其他值时,使用单位坐标。
例如,图层的anchorPoint
属性指定相对于图层本身边界的点,该点可能会发生变化。
基于点的坐标的最常见用途之一是指定图层的大小和位置,您可以使用图层的bounds
和position
属性来执行此操作。
bounds
定义图层本身的坐标系,并包含图层在屏幕上的大小。
position
属性定义图层相对于其父级坐标系的位置。
虽然图层具有 frame
属性,但该属性实际上是从 bounds
和position
属性中的值派生而来的,并且使用频率较低。
图层bounds
和frame
矩形的方向始终与底层平台的默认方向一致。
图 1-3显示了 iOS 和 OS X 上边界矩形的默认方向。
在 iOS 中,边界矩形的原点默认位于图层的左上角,而在 OS X 中则位于左下角。
如果您在 iOS 和 OS X 版本的应用中共享 Core Animation 代码,则必须考虑到这些差异。
图 1-3 iOS 和 OS X 的默认图层几何形状
[图 1-3] 中需要注意的一点是, position
属性位于图层的中间。
该属性是多个属性之一,其定义会根据图层anchorPoint
属性中的值而变化。
锚点表示某些坐标的起点,在锚点影响几何操作中有更详细的描述。
锚点是使用单位坐标系指定的几个属性之一。
Core Animation 使用单位坐标来表示那些值可能随图层大小变化而变化的属性。
你可以将单位坐标视为指定总可能值的百分比。
单位坐标空间中的每个坐标都有0.0
到 1.0
的范围。
例如,沿 x 轴,左边缘位于坐标0.0
,右边缘位于坐标1.0
。
沿 y 轴,单位坐标值的方向根据平台而变化,如图1-4所示。
图 1-4 iOS 和 OS X 默认单位坐标系统
注意: 在 OS X 10.8 之前,geometryFlipped
属性是一种在需要时更改图层 y 轴默认方向的方法。
当涉及翻转变换时,有时需要使用此属性来更正图层的方向。
例如,如果父视图使用翻转变换,其子视图(及其对应的图层)的内容通常会被反转。
在这种情况下,将子图层的geometryFlipped
属性设置为是YES
一种简单的更正问题的方法。
在 OS X 10.8 及更高版本中,AppKit 会为您管理此属性,您不应修改它。
对于 iOS 应用,建议您根本不要使用geometryFlipped
属性。
所有坐标值(无论是点还是单位坐标)都指定为浮点数。
使用浮点数允许您指定可能介于正常坐标值之间的精确位置。
使用浮点值很方便,尤其是在打印期间或在 Retina 显示屏上绘制时,一个点可能由多个像素表示。
浮点值允许您忽略底层设备分辨率,只需指定所需精度的值。
锚点影响几何操作
图层的几何相关操作是相对于该图层的锚点进行的,您可以使用图层的anchorPoint
属性访问该锚点。
在操作图层的position
或transform
属性时,锚点的影响最为明显。
position 属性始终是相对于图层的锚点指定的,并且您对图层应用的任何变换也是相对于锚点进行的。
图 1-5演示了将锚点从其默认值更改为其他值如何影响图层的position
属性。
即使图层没有在其父级的边界内移动,将锚点从图层的中心移动到图层的边界原点也会改变position
属性中的值。
图 1-5 锚点如何影响图层的位置属性
图 1-6显示了更改锚点如何影响应用于图层的变换。
当您对图层应用旋转变换时,旋转将围绕锚点进行。
由于锚点默认设置为图层的中间,因此这通常会产生您期望的旋转行为。
但是,如果您更改锚点,旋转的结果会有所不同。
图 1-6 锚点如何影响图层变换
可以在三维空间中操纵图层
每个图层都有两个变换矩阵,您可以使用它们来操作图层及其内容。
CALayer
的 transform
属性 指定要应用于图层 及其嵌入子图层的变换。
通常,当您想要修改图层本身时,您会使用此属性。
例如,您可以使用该属性来缩放或旋转图层或临时更改其位置。
sublayerTransform
属性定义仅适用于子图层的附加变换,最常用于为场景的内容添加透视视觉效果。
变换的工作原理是将坐标值与数字矩阵相乘,以获得表示原始点的变换版本的新坐标。
由于 Core Animation 值可以在三维中指定,因此每个坐标点都有四个值,必须通过四乘四矩阵相乘,如图1-7所示。
在 Core Animation 中,图中的变换由CATransform3D
类型表示。
幸运的是,您不必直接修改此结构的字段来执行标准变换。
Core Animation 提供了一套全面的函数来创建缩放、平移和旋转矩阵以及进行矩阵比较。
除了使用函数操作变换之外,Core Animation 还扩展了键值编码支持,允许您使用键路径修改变换。
有关您可以修改的键路径的列表,请参阅 CATransform3D Key Paths。
图 1-7 使用矩阵数学转换坐标
图 1-8显示了您可以进行的一些较常见变换的矩阵配置。
将任何坐标乘以恒等变换都会返回完全相同的坐标。
对于其他变换,坐标的修改方式完全取决于您更改的矩阵组件。
例如,要仅沿 x 轴平移,您需要为平移矩阵的tx
组件提供一个非零值,并将ty
和tz
值保留为 0。
对于旋转,您需要提供目标旋转角度的适当正弦和余弦值。
图 1-8 常见变换的矩阵配置
有关用于创建和操作变换的函数的信息,请参阅核心动画函数参考。
3、图层树反映动画状态的不同方面
使用 Core Animation 的应用有三组图层对象。
每组图层对象在使应用内容显示在屏幕上方面发挥着不同的作用:
- 模型层树(或简称“层树”)中的对象是您的应用最常与之交互的对象。
此树中的对象是存储任何动画的目标值的模型对象。
每当您更改层的属性时,都会使用其中一个对象。 - 演示树 中的对象包含任何正在运行的动画的实时值。
虽然图层树对象包含动画的目标值,但演示树中的对象反映的是屏幕上显示的当前值。
您永远不应修改此树中的对象。
相反,您可以使用这些对象来读取当前动画值,或许可以从这些值开始创建新动画。 - 渲染树 中的对象执行实际的动画并且是核心动画私有的。
每组图层对象都像应用中的视图一样被组织成一个层次结构。
事实上,对于为其所有视图启用图层的应用,每棵树的初始结构与视图层次结构完全匹配。
但是,应用可以根据需要在图层层次结构中添加其他图层对象(即与视图无关的图层)。
在某些情况下,您可能会这样做,以优化应用对不需要所有视图开销的内容的性能。
图 1-9显示了简单 iOS 应用中图层的细分。
示例中的窗口包含一个内容视图,该内容视图本身包含一个按钮视图和两个独立的图层对象。
每个视图都有一个相应的图层对象,它们构成了图层层次结构的一部分。
图 1-9 与窗口关联的图层
对于层树中的每个对象,在表示树和渲染树中都有一个匹配的对象,如图1-10所示。
如前所述,应用程序主要使用层树中的对象,但有时可能会访问表示树中的对象。
具体来说,访问层树中对象的presentationLayer
属性将返回表示树中的相应对象。
您可能希望访问该对象以读取动画中间的属性的当前值。
图 1-10 窗口的图层树
重要提示: 您应仅在动画运行时访问演示树中的对象。
动画进行过程中,演示树包含图层值,因为它们会立即显示在屏幕上。
此行为与图层树不同,图层树始终反映代码设置的最后一个值,相当于动画的最终状态。
4、层和视图之间的关系
图层不能替代应用的视图,也就是说,您无法仅基于图层对象创建可视化界面。
图层为您的视图提供基础结构。
具体来说,图层使绘制和动画视图内容并同时保持高帧率变得更容易、更高效。
但是,图层无法完成很多事情。
图层不处理事件、绘制内容、参与响应链或执行许多其他操作。
因此,每个应用仍必须有一个或多个视图来处理这些类型的交互。
在 iOS 中,每个视图都由相应的图层对象支持,但在 OS X 中,您必须决定哪些视图应该有图层。
在 OS X v10.8 及更高版本中,为所有视图添加图层可能很有意义。
但是,您不需要这样做,并且仍然可以在开销不合理且不需要的情况下禁用图层。
图层确实会在一定程度上增加应用程序的内存开销,但它们的好处通常大于坏处,因此在禁用图层支持之前,最好先测试应用程序的性能。
当您为视图启用图层支持时,您将创建所谓的图层支持视图。
在图层支持的视图中,系统负责创建底层图层对象并使该图层与视图保持同步。
所有 iOS 视图都是图层支持的,OS X 中的大多数视图也是如此。
但是,在 OS X 中,您还可以创建图层托管视图,即您自己提供图层对象的视图。
对于图层托管视图,AppKit 对图层采取不干预的方式管理,不会根据视图更改对其进行修改。
注意: 对于支持图层的视图,建议您尽可能操作视图,而不是其图层。
在 iOS 中,视图只是图层对象的薄包装,因此您对图层进行的任何操作通常都可以正常工作。
但在 iOS 和 OS X 中,有些情况下,操作图层而不是视图可能无法产生预期的结果。
只要有可能,本文档就会指出这些陷阱并尝试提供帮助您解决它们的方法。
除了与视图关联的图层之外,您还可以创建没有对应视图的图层对象。
您可以将这些独立图层对象嵌入到应用中的任何其他图层对象中,包括与视图关联的图层对象。
您通常将独立图层对象用作特定优化路径的一部分。
例如,如果您想在多个地方使用同一张图片,您可以加载一次该图片并将其与多个独立图层对象关联,然后将这些对象添加到图层树中。
然后,每个图层都会引用源图片,而不是尝试在内存中创建该图片的副本。
有关如何为应用的视图启用图层支持的信息,请参阅 在应用中启用核心动画支持。
有关如何创建图层对象层次结构的信息以及何时可以执行此操作的提示,请参阅 构建图层层次结构。
三、设置图层对象
图层对象是使用 Core Animation 进行所有操作的核心。
图层管理应用程序的视觉内容,并提供修改内容样式和视觉外观的选项。
尽管 iOS 应用程序会自动启用图层支持,但 OS X 应用程序的开发人员必须明确启用它,然后才能使用性能优势。
启用后,您需要了解如何配置和操作应用程序的图层以获得所需的效果。
1、在你的应用中启用核心动画支持
在 iOS 应用中,核心动画始终处于启用状态,并且每个视图都由一个层支持。
在 OS X 中,应用必须通过执行以下操作明确启用核心动画支持:
- 链接到 QuartzCore 框架。
(iOS 应用程序只有在明确使用 Core Animation 接口时才必须链接到此框架。) - 通过执行以下操作之一为一个或多个
NSView
对象启用图层支持:- 在您的 nib 文件中,使用“视图效果”检查器为您的视图启用图层支持。
检查器会显示所选视图及其子视图的复选框。
建议您尽可能在窗口的内容视图中启用图层支持。 - 对于以编程方式创建的视图,调用视图的
setWantsLayer:
方法并传递一个值来YES
指示该视图应该使用图层。
- 在您的 nib 文件中,使用“视图效果”检查器为您的视图启用图层支持。
通过上述任一方式启用层支持即可创建层支持视图。
使用层支持视图,系统负责创建底层层对象并保持该层更新。
在 OS X 中,还可以创建层托管视图,这样您的应用就可以实际创建和管理底层层对象。
(您无法在 iOS 中创建层托管视图。)有关如何创建层托管视图的更多信息,请参阅 层托管让您在 OS X 中更改层对象。
2、更改与视图关联的图层对象
层支持的视图默认会创建CALayer
类的实例,在大多数情况下,您可能不需要不同类型的层对象。
但是,Core Animation 提供了不同的层类,每个层类都提供了您可能会觉得有用的专门功能。
选择不同的层类可能会让您提高性能或以简单的方式支持特定类型的内容。
例如,CATiledLayer
类 针对以高效方式显示大图像进行了优化。
更改 UIView 使用的 Layer 类
您可以通过重写视图的layerClass
方法并返回不同的类对象来更改 iOS 视图使用的图层类型。
大多数 iOS 视图都会创建一个CALayer
对象 并使用该图层作为其内容的后备存储。
对于您自己的大多数视图来说,这个默认选择是不错的选择,您不需要更改它。
但您可能会发现在某些情况下,不同的图层类更合适。
例如,您可能希望在以下情况下更改图层类:
- 您的视图使用 Metal 或 OpenGL ES 绘制内容,在这种情况下您将使用
CAMetalLayer
或CAEAGLLayer
对象。 - 有一个专门的层类可以提供更好的性能。
- 您想使用一些专门的核心动画层类,例如粒子发射器或复制器。
更改视图的图层类非常简单;示例如例 2-1所示。
您所要做的就是重写 layerClass
方法并返回要使用的类对象。
在显示之前,视图会调用 layerClass
方法并使用返回的类为自己创建一个新的图层对象。
一旦创建,视图的图层对象就无法更改。
例 2-1 指定 iOS 视图的图层类
+ (Class) layerClass {
return [CAMetalLayer class];
}
有关图层类的列表以及如何使用它们,请参阅 不同的图层类提供专门的行为。
更改 NSView 使用的 Layer 类
您可以通过重写makeBackingLayer
方法来更改NSView
对象使用的默认图层类。
在该方法的实现中,创建并返回您希望 AppKit 用来支持您的自定义视图的图层对象。
在您想要使用自定义图层(例如滚动图层或平铺图层)的情况下,您可以重写此方法。
有关图层类的列表以及如何使用它们,请参阅 不同的图层类提供专门的行为。
层托管可让您在 OS X 中更改层对象
层托管视图是 您自己创建和管理底层层对象的NSView
对象。
在您想要控制与视图关联的层对象类型的情况下,可以使用层托管。
例如,您可以创建层托管视图,以便可以分配默认CALayer
类以外的层类。
在您想要使用单个视图来管理独立层层次结构的情况下,也可以使用它。
当您调用视图的setLayer:
方法并提供图层对象时,AppKit 会对该图层采取不干预的方式。
通常,AppKit 会更新视图的图层对象,但在图层托管情况下,它不会更新大多数属性。
要创建图层托管视图,请在屏幕上显示视图之前创建图层对象并将其与视图关联,如例 2-2所示。
除了设置图层对象之外,您还必须调用setWantsLayer:
方法 让视图知道它应该使用图层。
例 2-2 创建层托管视图
// Create myView...
[myView setWantsLayer:YES];
CATiledLayer* hostedLayer = [CATiledLayer layer];
[myView setLayer:hostedLayer];
// Add myView to a view hierarchy.
如果您选择自行托管图层,则必须自行设置contentsScale
属性并在适当的时候提供高分辨率内容。
有关高分辨率内容和比例因子的更多信息,请参阅 使用高分辨率图像。
不同的图层类别提供专门的行为
Core Animation 定义了许多标准图层类,每个类都是为特定用例而设计的。
该类是所有图层对象的根类。
它定义了所有图层对象必须支持的行为,并且是支持图层的视图使用的默认类型。
不过,您也可以指定表 2-1CALayer
中的图层类之一。
Table 2-1 CALayer subclasses and their uses
班级 | 用法 |
---|---|
CAEmitterLayer | 用于实现基于 Core Animation 的粒子发射器系统。 |
发射器层对象控制粒子的生成及其来源。 | |
CAGradientLayer | 用于绘制填充图层形状的颜色渐变(在任何圆角的边界内)。 |
CAMetalLayer | 用于设置和提供可绘制纹理,以便使用 Metal 渲染图层内容。 |
CAEAGLLayer /CAOpenGLLayer | 用于设置使用 OpenGL ES(iOS)或 OpenGL(OS X)渲染层内容的后备存储和上下文。 |
CAReplicatorLayer | 当您想要自动复制一个或多个子图层时使用。 复制器会为您制作副本并使用您指定的属性来更改副本的外观或属性。 |
CAScrollLayer | 用于管理由多个子层组成的大型可滚动区域。 |
CAShapeLayer | 用于绘制三次贝塞尔样条线。 形状图层有利于绘制基于路径的形状,因为它们总是会产生清晰的路径,而不是绘制到图层后备存储中的路径,后者在缩放时看起来不太好。 但是,清晰的结果确实涉及在主线程上渲染形状并缓存结果。 |
CATextLayer | 用于呈现纯文本或属性文本字符串。 |
CATiledLayer | 用于管理大图像,该图像可分成较小的图块并单独渲染,并支持放大和缩小内容。 |
CATransformLayer | 用于渲染真正的 3D 图层层次结构,而不是其他图层类实现的扁平图层层次结构。 |
QCCompositionLayer | 用于渲染 Quartz Composer 合成。(仅限 OS X) |
3、提供图层的内容
图层是管理应用提供的内容的数据对象。
图层的内容由包含要显示的视觉数据的位图组成。
您可以通过以下三种方式之一为该位图提供内容:
- 将图像对象直接分配给图层对象的
contents
属性。
(此技术最适合从不或很少改变的图层内容。) - 将代理对象分配给图层并让代理绘制图层的内容。
(此技术最适合可能定期更改且可由外部对象(例如视图)提供的图层内容。) - 定义一个图层子类并重写其中的一个绘制方法,以便自己提供图层内容。
(如果您无论如何都要创建自定义图层子类,或者想要更改图层的基本绘制行为,则此技术非常适用。)
您唯一需要担心为图层提供内容的情况是当您自己创建图层对象时。
如果您的应用只包含图层支持的视图,则您不必担心使用上述任何技术来提供图层内容。
图层支持的视图会以最高效的方式自动为其关联的图层提供内容。
使用图像作为图层的内容
由于图层只是用于管理位图图像的容器,因此您可以直接将图像分配给图层的contents
属性。
将图像分配给图层非常简单,您可以指定要在屏幕上显示的确切图像。
图层直接使用您提供的图像对象,而不会尝试创建该图像的副本。
如果您的应用在多个地方使用同一图像,此行为可以节省内存。
您分配给图层的图像必须是某种CGImageRef
类型。
(在 OS X v10.6 及更高版本中,您还可以分配NSImage
对象。)分配图像时,请记住提供分辨率与本机设备分辨率相匹配的图像。
对于配备 Retina 显示屏的设备,这可能还需要您调整图像的contentsScale
属性。
有关在图层中使用高分辨率内容的信息,请参阅 使用高分辨率图像。
使用代理提供图层的内容
如果图层的内容动态变化,您可以使用代理对象在需要时提供和更新该内容。
在显示时,图层会调用代理的方法来提供所需的内容:
- 如果您的代理实现
displayLayer:
方法,则该实现负责创建位图并将其分配给图层的contents
属性。 - 如果您的代理实现了
drawLayer:inContext:
方法,Core Animation 将创建一个位图,创建一个图形上下文来绘制该位图,然后调用您的代理方法来填充该位图。
您的代理方法所要做的就是在提供的图形上下文中绘制。
代理对象必须实现 或displayLayer:
方法drawLayer:inContext:
。
如果代理同时实现displayLayer:
和drawLayer:inContext:
方法,则该层仅调用displayLayer:
方法。
当您的应用希望加载或创建想要显示的位图时,需要重写displayLayer:
方法。
例 2-3显示了代理方法的示例实现displayLayer:
。
在此示例中,代理使用辅助对象来加载和显示所需的图像。
代理方法根据其自身的内部状态选择要显示的图像,在示例中,内部状态是名为 displayYesImage
的自定义属性。
例 2-3 直接设置图层内容
- (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 绘制图层的内容
- (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);
}
对于具有自定义内容的层支持视图,您应该继续重写视图的方法来绘制。
层支持视图会自动将自己设为其层的代理并实现所需的代理方法,您不应更改该配置。
相反,您应该实现视图的drawRect:
方法来绘制内容。
在 OS X v10.8 及更高版本中,绘制的替代方法是通过重写视图的wantsUpdateLayer
和updateLayer
方法来提供位图。
重写wantsUpdateLayer
和返回YES
会导致NSView
类 遵循备用渲染路径。
视图不会调用drawRect:
,而是调用您的updateLayer
方法,该方法的实现必须将位图直接分配给图层的contents
属性。
这是 AppKit 希望您直接设置视图图层对象内容的一种情况。
通过子类化提供图层内容
如果您无论如何都要实现自定义图层类,则可以重写图层类的绘制方法来进行任何绘制。
图层对象本身生成自定义内容的情况并不常见,但图层当然可以管理内容的显示。
例如,CATiledLayer
类 通过将大图像分解为可以单独管理和渲染的较小图块来管理大图像。
因为只有图层才知道在给定时间需要渲染哪些图块,所以它直接管理绘制行为。
当进行子类化时,你可以使用下列任一技术来绘制图层的内容:
- 重写图层的
display
方法,并使用它来直接设置图层的contents
属性。 - 重写图层的
drawInContext:
方法,并使用它来绘制到提供的图形上下文中。
重写哪种方法取决于 您需要对绘制过程进行多少控制。
display
方法是更新图层内容的主要入口点,因此重写该方法可让您完全控制该过程。
重写 display
方法还意味着 您负责创建CGImageRef
要分配给contents
属性的。
如果您只想绘制内容(或让您的图层管理绘制操作),您可以重写 drawInContext:
方法并让图层为您创建后备存储。
调整您提供的内容
当您将图像分配给图层的contents
属性时,图层的contentsGravity
属性将决定如何操作该图像以适应当前边界。
默认情况下,如果图像大于或小于当前边界,图层对象将缩放图像以适应可用空间。
如果图层边界的宽高比与图像的宽高比不同,则会导致图像扭曲。
您可以使用 contentsGravity
属性来确保您的内容以最佳方式呈现。
可以分配给contentsGravity
属性的值分为两类:
- 基于位置的重力常数允许您将图像固定到图层边界矩形的特定边缘或角落,而无需缩放图像。
- 基于缩放的重力常数允许您使用几个选项之一来拉伸图像,其中一些选项会保留纵横比,而一些则不会。
图 2-1显示了基于位置的重力设置如何影响您的图像。
除了kCAGravityCenter
常量之外,每个常量都将图像固定到图层边界矩形的特定边缘或角落。
kCAGravityCenter
常量将图像置于图层的中心。
这些常量都不会以任何方式导致图像缩放,因此图像始终以其原始大小呈现。
如果图像大于图层的边界,则可能导致图像的部分被剪切,如果图像较小,则图像未被图像重写的图层部分将显示图层的背景颜色(如果已设置)。
图 2-1 基于位置的图层重力常数
图 2-2显示了基于缩放的重力常量如何影响您的图像。
如果图像不能完全适合图层的边界矩形,所有这些常量都会缩放图像。
这些模式之间的区别在于它们如何处理图像的原始纵横比。
有些模式会保留它,而其他模式则不会。
默认情况下,图层的contentsGravity
属性设置为常量kCAGravityResize
,这是唯一不保留图像纵横比的模式。
图 2-2 基于缩放的层的重力常数
使用高分辨率图像
图层本身并不了解底层设备屏幕的分辨率。
图层只是存储指向位图的指针,并根据可用像素以最佳方式显示它。
如果将图像分配给图层的contents
属性,则必须通过将图层的contentsScale
属性设置为适当的值来告知 Core Animation 图像的分辨率。
该属性的默认值为1.0
,适用于要在标准分辨率屏幕上显示的图像。
如果您的图像用于 Retina 显示屏,请将此属性的值设置为2.0
。
仅当您直接将位图分配给图层时,才需要更改contentsScale
属性的值。
UIKit 和 AppKit 中支持图层的视图会根据屏幕分辨率和视图管理的内容自动将其图层的比例因子设置为适当的值。
例如,如果您将NSImage
对象分配给OS X 中图层的contents
属性,AppKit 会查看图像是否同时存在标准分辨率和高分辨率变体。
如果有,AppKit 会使用当前分辨率的正确变体并设置contentsScale
属性的值 以匹配。
在 OS X 中,基于位置的重力常量 会影响从分配给图层的NSImage
对象中选择图像表示的方式。
由于这些常量不会导致图像缩放,因此 Core Animation 依靠 contentsScale
属性来选择具有最合适像素密度的图像表示。
在 OS X 中,图层的代理可以实现 layer:shouldInheritContentsScale:fromWindow:
方法并使用它来响应比例因子的变化。
每当给定窗口的分辨率发生变化时,AppKit 都会自动调用该方法,这可能是因为窗口在标准分辨率和高分辨率屏幕之间移动。
如果代理支持更改图层图像的分辨率,则此方法的实现应该返回YES
。
然后,该方法应根据需要更新图层的内容以反映新的分辨率。
4、调整图层的视觉样式和外观
Layer 对象具有内置的视觉修饰,例如边框和背景颜色,您可以使用它们来补充图层的主要内容。
由于这些视觉修饰不需要您进行任何渲染,因此在某些情况下可以将图层用作独立实体。
您只需在图层上设置一个属性,图层就会处理必要的绘制,包括任何动画。
有关这些视觉修饰如何影响图层外观的更多说明,请参阅 图层样式属性动画。
图层有自己的背景和边框
图层除了显示基于图像的内容外,还可以显示填充的背景和描边边框。
背景颜色渲染在图层内容图像的后面,边框渲染在图像的顶部,如图2-3所示。
如果图层包含子图层,子图层也会显示在边框下方。
由于背景颜色位于图像后面,因此该颜色可以透过图像的任何透明部分。
图 2-3 为图层添加边框和背景
例 2-5显示了设置图层背景颜色和边框所需的代码。
所有这些属性都是可动画的。
例 2-5 设置图层的背景颜色和边框
myLayer.backgroundColor = [NSColor greenColor].CGColor;
myLayer.borderColor = [NSColor blackColor].CGColor;
myLayer.borderWidth = 3.0;
注意: 您可以使用任何类型的颜色作为图层的背景,包括具有透明度的颜色或使用图案图像。
但是,在使用图案图像时,请注意 Core Graphics 会处理图案图像的渲染,并使用其标准坐标系进行渲染,这与 iOS 中的默认坐标系不同。
因此,除非您翻转坐标,否则 iOS 上渲染的图像默认会上下颠倒。
如果您将图层的背景颜色设置为不透明颜色,请考虑将图层的 opaque 属性设置为YES
。
这样做可以提高在屏幕上合成图层时的性能,并且无需图层的后备存储来管理 alpha 通道。
但是,如果图层的角半径也为非零,则不得将其标记为不透明。
图层支持圆角
您可以通过为图层添加圆角半径来创建圆角矩形效果。
圆角半径是一种视觉装饰,它会遮盖图层边界矩形的部分角,以允许底层内容显示出来,如图2-4所示。
由于它涉及应用透明蒙版,因此圆角半径不会影响图层contents
属性中的图像,除非 masksToBounds
属性设置为YES
。
但是,圆角半径始终会影响图层背景颜色和边框的绘制方式。
图 2-4 图层上的圆角半径
要将角半径应用于图层,请为图层的cornerRadius
属性指定一个值。
您指定的半径值以点为单位测量,并在显示之前应用于图层的所有四个角。
图层支持内置阴影
该类CALayer
包含几个用于配置阴影效果的属性。
阴影通过使图层看起来好像漂浮在其底层内容之上来增加其深度。
这是另一种视觉装饰,您可能会发现它在特定情况下对应用很有用。
使用图层,您可以控制阴影的颜色、相对于图层内容的位置、不透明度和形状。
图层阴影的不透明度值默认设置为0
,这样可以有效隐藏阴影。
将不透明度更改为非零值会导致 Core Animation 绘制阴影。
由于阴影默认位于图层正下方,因此可能还需要更改阴影的偏移量才能看到它。
但请务必记住,您为阴影指定的偏移量是使用图层的原生坐标系应用的,这在 iOS 和 OS X 上是不同的。
图 2-5显示了具有向下和向右延伸的阴影的图层。
在 iOS 中,这需要为 y 轴指定一个正值,但在 OS X 中,该值需要为负值。
图 2-5 为图层添加阴影
向图层添加阴影时,阴影是图层内容的一部分,但实际上延伸到了图层边界矩形之外。
因此,如果启用图层的masksToBounds
属性,阴影效果会在边缘处被剪切。
如果图层包含任何透明内容,这可能会导致奇怪的效果,即图层正下方的阴影部分仍然可见,但延伸到图层之外的部分则不可见。
如果您想要阴影但也想使用边界遮罩,则可以使用两个图层而不是一个。
将遮罩应用到包含内容的图层,然后将该图层嵌入到启用了阴影效果的完全相同大小的第二个图层中。
有关如何将阴影应用于图层的示例,请参阅 阴影属性。
过滤器为 OS X 视图添加视觉效果
在 OS X 应用中,您可以将 Core Image 滤镜直接应用于图层的内容。
您可以这样做来模糊或锐化图层的内容、更改颜色、扭曲内容或执行许多其他类型的操作。
例如,图像处理程序可以使用这些滤镜以非破坏性方式修改图像,而视频编辑程序可以使用它们来实现不同类型的视频过渡效果。
而且由于滤镜是在硬件中应用于图层的内容,因此渲染速度快且流畅。
注意: 您不能向 iOS 中的图层对象添加过滤器。
对于给定的图层,您可以将滤镜应用于图层的前景和背景内容。
前景内容由图层本身包含的所有内容组成,包括其contents
属性中的图像、背景颜色、边框以及子图层的内容。
背景内容是直接位于图层下方但实际上不是图层本身的一部分的内容。
大多数图层的背景内容是其直接上级图层的内容,可能被图层全部或部分遮挡。
例如,当您希望用户专注于图层的前景内容时,您可能会将模糊滤镜应用于背景内容。
您可以通过CIFilter
向图层的以下属性添加对象来指定过滤器:
filters
属性包含仅影响图层前景内容的过滤器数组。backgroundFilters
属性包含仅影响图层背景内容的过滤器数组。compositingFilter
属性定义图层的前景和背景内容如何合成在一起。
要将滤镜添加到图层,您必须先找到并创建CIFilter
对象,然后对其进行配置,然后再将其添加到图层。
CIFilter
类包括几种用于定位可用 Core Image 滤镜的类方法,例如filterWithName:
方法。
不过,创建滤镜只是第一步。
许多滤镜都有定义滤镜如何修改图像的参数。
例如,方框模糊滤镜有一个输入半径参数,它会影响应用的模糊量。
您应该始终在滤镜配置过程中为这些参数提供值。
但是,您不需要指定的一个常见参数是输入图像,它由图层本身提供。
在向图层添加滤镜时,最好在将滤镜添加到图层之前配置滤镜参数。
这样做的主要原因是,一旦添加到图层,您就无法修改CIFilter
对象本身。
但是,您可以使用图层的setValue:forKeyPath:
方法在事后更改滤镜值。
例 2-6显示了如何创建和应用挤压变形滤镜到图层对象。
此滤镜将图层的源像素向内挤压,使最靠近指定中心点的像素变形最大。
请注意,示例中您无需为滤镜指定输入图像,因为会自动使用图层的图像。
例 2-6 将过滤器应用于图层
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];
有关可用的核心图像过滤器的信息,请参阅 核心图像过滤器参考。
5、OS X 视图的图层重绘策略影响性能
在 OS X 中,层支持视图支持多种不同的策略来确定何时更新底层图层的内容。
由于原生 AppKit 绘图模型与 Core Animation 引入的模型之间存在差异,因此这些策略可让您更轻松地将旧代码迁移到 Core Animation。
您可以逐个视图配置这些策略,以确保每个视图的最佳性能。
每个视图都定义了一个layerContentsRedrawPolicy
方法,该方法返回视图图层的重绘策略。
您可以使用 setLayerContentsRedrawPolicy:
方法设置策略。
为了保持与传统绘图模型的兼容性,AppKit 默认将重绘策略设置为NSViewLayerContentsRedrawDuringViewResize
。
但是,您可以将策略更改为表 2-2 中的任何值。
请注意,推荐的重绘策略不是默认策略。
Table 2-2 Layer redraw policies for OS X views
政策 | 用法 |
---|---|
NSViewLayerContentsRedrawOnSetNeedsDisplay | 这是推荐的策略。 使用此策略,视图几何形状的更改不会自动导致视图更新其图层的内容。 相反,图层的现有内容会被拉伸和操纵以促进几何形状的更改。 要强制视图重新绘制自身并更新图层的内容,您必须明确调用视图的 setNeedsDisplay: 方法。 此策略最接近代表核心动画图层的标准行为。 但是,它不是默认策略,必须明确设置。 |
NSViewLayerContentsRedrawDuringViewResize | 这是默认的重绘策略。 此策略通过在视图的几何形状发生变化时重新缓存图层的内容来保持与传统 AppKit 绘图的最大兼容性。 此行为会导致 drawRect: 在调整大小操作期间在应用的主线程上多次调用视图的方法。 |
NSViewLayerContentsRedrawBeforeViewResize | 使用此策略,AppKit 会在执行任何调整大小操作之前以最终大小绘制图层并缓存该位图。 调整大小操作使用缓存的位图作为起始图像,将其缩放以适合旧边界矩形。 然后,它会将位图动画化为其最终大小。 此行为可能会导致视图的内容在动画开始时出现拉伸或扭曲,并且在初始外观不重要或不明显的情况下效果会更好。 |
NSViewLayerContentsRedrawNever | 使用此策略,即使您调用该方法,AppKit 也不会更新图层setNeedsDisplay: 。 此策略最适合内容永不改变且视图大小很少改变(甚至根本不改变)的视图。 例如,您可以将此策略用于显示固定大小内容或背景元素的视图。 |
视图重绘策略减轻了使用独立子层来提高绘制性能的需要。
在引入视图重绘策略之前,有些层支持视图的绘制频率超过了需要的频率,从而导致了性能问题。
解决这些性能问题的方法是使用子层来呈现视图内容中不需要定期重绘的部分。
随着 OS X v10.6 中重绘策略的引入,现在建议您将层支持视图的重绘策略设置为适当的值,而不是创建显式子层层次结构。
6、向层添加自定义属性
CAAnimation
和CALayer
类扩展了键值编码约定以支持自定义属性。
您可以使用此行为将数据添加到图层并使用您定义的自定义键检索数据。
您甚至可以将操作与自定义属性关联起来,这样当您更改属性时,就会执行相应的动画。
有关如何设置和获取自定义属性的信息,请参阅 兼容键值编码的容器类。
有关向图层对象添加操作的信息,请参阅 更改图层的默认行为。
7、打印基于图层的视图的内容
在打印过程中,图层会根据需要重新绘制其内容以适应打印环境。
虽然 Core Animation 在渲染到屏幕时通常依赖于缓存的位图,但它会在打印时重新绘制该内容。
特别是,如果层支持的视图使用 drawRect:
方法提供图层内容,Core Animation 会在打印期间再次调用drawRect:
以生成打印的图层内容。
四、动画图层内容
Core Animation 提供的基础结构让您能够轻松为应用的图层创建复杂的动画,并扩展到拥有这些图层的任何视图。
示例包括更改图层框架矩形的大小、更改其在屏幕上的位置、应用旋转变换或更改其不透明度。
使用 Core Animation,启动动画通常就像更改属性一样简单,但您也可以创建动画并明确设置动画参数。
有关创建更高级动画的信息,请参阅 高级动画技巧。
1、为图层属性的简单变化制作动画
您可以根据需要以隐式或显式方式执行简单动画。
隐式动画使用默认的计时和动画属性来执行动画,而显式动画则要求您使用动画对象自行配置这些属性。
因此,隐式动画非常适合您想要在不写大量代码的情况下进行更改并且默认计时效果很好的情况。
简单动画涉及更改图层的属性,并让 Core Animation 随时间对这些更改进行动画处理。
图层定义了许多影响图层可见外观的属性。
更改其中一个属性是动画外观变化的一种方法。
例如,将图层的不透明度从1.0
更改为0.0
,会导致图层淡出并变为透明。
重要提示: 虽然有时可以使用 Core Animation 界面直接为基于图层的视图制作动画,但这样做通常需要额外的步骤。
有关如何将 Core Animation 与基于图层的视图结合使用的更多信息,请参阅 如何为基于图层的视图制作动画。
要触发隐式动画,您只需更新图层对象的属性即可。
在图层树中修改图层对象时,您的更改会立即反映在这些对象上。
但是,图层对象的视觉外观不会立即改变。
相反,Core Animation 会使用您的更改作为触发器来创建和安排一个或多个隐式动画以供执行。
因此,进行例 3-1中的更改会导致 Core Animation 为您创建一个动画对象并安排该动画从下一个更新周期开始运行。
例 3-1 隐式地动画改变
theLayer.opacity = 0.0;
要使用动画对象明确地做出相同的更改,请创建一个CABasicAnimation
对象并使用该对象配置动画参数。
在将动画添加到图层之前,您可以设置动画的开始值和结束值、更改持续时间或更改任何其他动画参数。
例 3-2显示了如何使用动画对象淡出图层。
创建对象时,您可以指定要动画的属性的关键路径,然后设置动画参数。
要执行动画,您可以使用addAnimation:forKey:
方法 将其添加到要动画的图层。
例 3-2 明确地为变化添加动画
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 Animation 会从图层中移除动画对象并使用其当前数据值重新绘制图层。
如果您希望显式动画的更改永久生效,还必须更新图层的属性,如上例所示。
隐式和显式动画通常在当前运行循环周期结束后开始执行,并且当前线程必须具有运行循环才能执行动画。
如果您更改多个属性,或者向图层添加多个动画对象,则所有这些属性更改都会同时进行动画处理。
例如,您可以通过同时配置两个动画来淡化图层并将其移出屏幕。
但是,您也可以将动画对象配置为在特定时间开始。
有关修改动画时间的更多信息,请参阅 自定义动画时间。
2、使用关键帧动画更改图层属性
基于属性的动画会将属性从起始值更改为终止值,而CAKeyframeAnimation
对象则允许您以线性或非线性的方式通过一组目标值进行动画处理。
关键帧动画由一组目标数据值以及应达到每个值的时间组成。
在最简单的配置中,您可以使用数组指定值和时间。
对于图层位置的更改,您还可以让更改遵循路径。
动画对象采用您指定的关键帧,并通过在给定的时间段内从一个值插入到下一个值来构建动画。
图 3-1显示了图层position
属性的 5 秒动画。
动画中的位置跟随使用CGPathRef
数据类型 指定的路径移动。
此动画的代码如例 3-3所示。
图 3-1 图层位置属性的 5 秒关键帧动画
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/Art/keyframepath-anim.m4v
例 3-3展示了实现图 3-1中动画的代码。
本例中的路径对象用于定义动画每一帧的图层位置。
例 3-3 创建弹跳关键帧动画
// 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"];
指定关键帧值
关键帧值是关键帧动画中最重要的部分。
这些值定义了动画在执行过程中的行为。
指定关键帧值的主要方法是将其作为对象数组,但对于包含数据类型CGPoint
(例如图层anchorPoint
和position
属性)的值,您可以改为指定CGPathRef
数据类型。
指定值数组时,放入数组的内容取决于属性所需的数据类型。
您可以直接将一些对象添加到数组中;但是,有些对象必须在添加之前转换为id
,并且所有标量类型或结构都必须由对象包装。
例如:
- 对于采用的
CGRect
属性(例如边界和框架属性),将每个矩形包装在一个NSValue
对象中。 - 对于图层的变换属性,将每个
CATransform3D
矩阵包装在一个NSValue
对象中。
对此属性进行动画处理会导致关键帧动画依次将每个变换矩阵应用于图层。 - 对于
borderColor
属性,在将每个CGColorRef
数据类型添加到数组之前,将其转换为id
类型。 - 对于具有
CGFloat
值的属性,请先将每个值包装在NSNumber
对象中,然后再将其添加到数组中。 - 在为图层的
contents
属性设置动画时,指定一个CGImageRef
数据类型数组。
对于采用CGPoint
数据类型的属性,您可以创建一个点数组(包裹在NSValue
对象中),也可以使用CGPathRef
对象来指定要遵循的路径。
当您指定一个点数组时,关键帧动画对象会在每个连续点之间绘制一条直线并遵循该路径。
当您指定一个CGPathRef
对象时,动画会从路径的起点开始并遵循其轮廓,包括沿着任何曲面。
您可以使用开放或封闭的路径。
指定关键帧动画的时间
关键帧动画的时间和节奏比基本动画更复杂,并且您可以使用多个属性来控制它:
calculationMode
属性定义用于计算动画时间的算法。
此属性的值会影响其他与时间相关的属性的使用方式。- 线性动画和立方体动画(即
calculationMode
属性设置为kCAAnimationLinear
或kCAAnimationCubic
的动画)使用提供的计时信息来生成动画。
这些模式可让您最大程度地控制动画计时。 - 定速动画(即
calculationMode
属性设置为kCAAnimationPaced
或kCAAnimationCubicPaced
的动画)不依赖于keyTimes
或timingFunctions
属性 提供的外部计时值。
相反,计时值是隐式计算的,以便为动画提供恒定的速度。 - 离散动画(即
calculationMode
属性 设置为kCAAnimationDiscrete
的动画)会导致动画属性从一个关键帧值跳转到下一个关键帧值,而无需任何插值。
此计算模式使用keyTimes
属性中的值,但忽略timingFunctions
属性
- 线性动画和立方体动画(即
keyTimes
属性指定应用每个关键帧值的时间标记。
仅当计算模式设置为kCAAnimationLinear
、kCAAnimationDiscrete
或kCAAnimationCubic
时才使用此属性。
它不用于定速动画。timingFunctions
属性指定每个关键帧段要使用的时序曲线。
(此属性取代了继承的timingFunction
属性。)
如果您想自己处理动画时间,请使用kCAAnimationLinear
或kCAAnimationCubic
模式以及keyTimes
和timingFunctions
属性。
keyTimes
定义应用每个关键帧值的时间点。
所有中间值的计时均由计时函数控制,这些函数允许您将缓入或缓出曲线应用于每个片段。
如果您未指定任何计时函数,则计时为线性。
3、停止正在运行的显式动画
动画通常会一直运行直到完成,但是如果需要,您可以使用以下技术之一提前停止它们:
- 要从图层中删除单个动画对象,请调用图层的
removeAnimationForKey:
方法来删除动画对象。
addAnimation:forKey:
方法使用传递给方法的键来标识动画。
您指定的键不能是nil
。 - 要从图层中删除所有动画对象,请调用图层的
removeAllAnimations
方法。
此方法会立即删除所有正在进行的动画,并使用其当前状态信息重新绘制图层。
注意: 您不能直接从图层中删除隐式动画。
当您从图层中删除动画时,Core Animation 会使用其当前值重新绘制图层。
由于当前值通常是动画的最终值,因此这可能会导致图层的外观突然跳变。
如果您希望图层的外观保持在动画最后一帧的位置,则可以使用演示树中的对象来检索这些最终值并将它们设置在图层树中的对象上。
有关暂时暂停动画的信息,请参见例 5-4。
4、同时为多个变化添加动画效果
如果要同时将多个动画应用于图层对象,可以使用CAAnimationGroup
对象将它们分组在一起。
使用组对象通过提供单个配置点简化了多个动画对象的管理。
应用于组的时间和持续时间值将重写单个动画对象中的相同值。
例 3-4展示了如何使用动画组同时以相同的持续时间执行两个与边框相关的动画。
例 3-4 同时播放两个动画
// 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"];
将动画组合在一起的更高级方法是使用事务对象。
事务通过允许您创建嵌套动画集并为每个动画集分配不同的动画参数来提供更大的灵活性。
有关如何使用事务对象的信息,请参阅 显式事务让您更改动画参数。
5、检测动画的结束
Core Animation 支持检测动画何时开始或结束。
这些通知非常适合执行与动画相关的任何日常任务。
例如,您可以使用开始通知来设置一些相关状态信息,并使用相应的结束通知来拆除该状态。
有两种不同的方式可以获知动画的状态:
- 使用
setCompletionBlock:
方法向当前事务添加完成块。
当事务中的所有动画完成后,事务将执行您的完成块。 - 为您的
CAAnimation
对象分配一个代理并实现animationDidStart:
和animationDidStop:finished:
代理方法。
如果您想要将两个动画串联在一起,以便一个动画在另一个动画结束时开始,请不要使用动画通知。
相反,请使用动画对象的beginTime
属性在所需的时间启动每个动画。
要将两个动画串联在一起,请将第二个动画的开始时间设置为第一个动画的结束时间。
有关动画和时间值的更多信息,请参阅 自定义动画的时间。
6、如何为基于图层的视图制作动画
如果图层属于图层支持视图,则建议使用 UIKit 或 AppKit 提供的基于视图的动画接口来创建动画。
有多种方法可以使用 Core Animation 接口直接为图层制作动画,但创建这些动画的方式取决于目标平台。
iOS 中修改图层的规则
由于 iOS 视图始终具有底层图层,因UIView
类本身直接从图层对象派生其大部分数据。
因此,您对图层所做的更改也会自动反映在视图对象中。
此行为意味着您可以使用 Core Animation 或UIView
接口来进行更改。
如果要使用 Core Animation 类来启动动画,则必须从基于视图的动画块内部发出所有 Core Animation 调用。
UIView
类默认禁用图层动画,但在动画块内部重新启用它们。
因此,您在动画块外部所做的任何更改都不会被动画化。
例 3-5显示了如何隐式更改图层的不透明度并显式更改其位置的示例。
在此示例中,myNewPosition
变量是预先计算并由块捕获的。
两个动画同时开始,但不透明度动画以默认时间运行,而位置动画以其动画对象中指定的时间运行。
例 3-5 对 附加到 iOS 视图的图层进行动画处理
[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 中修改图层的规则
要为 OS X 中层支持视图的更改添加动画效果,最好使用视图本身的接口。
您应尽量避免直接修改附加到层支持的NSView
对象之一的层。
AppKit 负责创建和配置这些层对象,并在应用运行时管理它们。
修改层可能会导致其与视图对象不同步,并可能导致意外结果。
对于层支持视图,您的代码绝对不能修改层对象的以下任何属性:
anchorPoint
bounds
compositingFilter
filters
frame
geometryFlipped
hidden
position
shadowColor
shadowOffset
shadowOpacity
shadowRadius
transform
重要提示: 上述限制不适用于图层托管视图。
如果您手动创建了图层对象并将其与视图关联,则您有责任修改该图层的属性并保持相应的视图对象同步。
AppKit 默认禁用其层支持视图的隐式动画。
视图的动画代理对象会自动为您重新启用隐式动画。
如果您想直接为图层属性设置动画,也可以通过将当前NSAnimationContext
对象的allowsImplicitAnimation
属性更改为YES
,以编程方式重新启用隐式动画。
同样,您只应对上述列表中未列出的可动画属性执行此操作。
记得在动画过程中更新视图约束
如果您使用基于约束的布局规则来管理视图的位置,则必须在配置动画时移除可能干扰动画的任何约束。
约束会影响您对视图的位置或大小所做的任何更改。
它们还会影响视图与其子视图之间的关系。
如果您要为其中任何一项的更改制作动画,则可以移除约束,进行更改,然后应用所需的任何新约束。
有关约束以及如何使用它们来管理视图布局的更多信息,请参阅 自动布局指南。
五、构建层级结构
大多数情况下,在应用中使用图层的最佳方式是将它们与视图对象结合使用。
但是,有时您可能需要通过向视图层次结构中添加其他图层对象来增强视图层次结构。
当这样做可以提供更好的性能或让您实现仅使用视图难以实现的功能时,您可能会使用图层。
在这些情况下,您需要知道如何管理您创建的图层层次结构。
重要提示: 在 OS X v10.8 及更高版本中,建议您尽量减少使用图层层次结构,只使用图层支持的视图。
该 OS X 版本中引入的图层重绘策略允许您自定义图层支持的视图的行为,同时仍可获得以前使用独立图层时可能获得的性能。
1、将图层排列成图层层次结构
图层层次结构在很多方面与视图层次结构相似。
您可以将一个图层嵌入另一个图层中,以在嵌入的图层(称为子图层)和父图层(称为超图层)之间创建父子关系。
这种父子关系会影响子图层的各个方面。
例如,其内容位于其父图层内容之上,其位置是相对于其父图层的坐标系指定的,并且会受到应用于父图层的任何变换的影响。
添加、插入和删除子层
每个图层对象都有添加、插入和移除子图层的方法。
表 4-1总结了这些方法及其行为。
Table 4-1 Methods for modifying the layer hierarchy
行为 | 方法 | 描述 |
---|---|---|
添加图层 | addSublayer: | 向当前图层添加新的子图层对象。 该子图层将添加到图层的子图层列表末尾。 这会导致子图层出现在其 zPosition 属性中具有相同值的任何同级图层之上。 |
插入图层 | insertSublayer:above:``insertSublayer:atIndex:``insertSublayer:below: | 将子图层插入到子图层层次结构中的指定索引处或相对于另一个子图层的位置。 当插入到另一个子图层的上方或下方时,您仅指定子图层在数组中的位置 sublayers 。图层的实际可见性主要由其属性中的值决定 zPosition ,其次由其在数组中的位置决定sublayers 。 |
移除图层 | removeFromSuperlayer | 从其父层中移除子层。 |
交换层 | replaceSublayer:with: | 将一个子图层交换为另一个子图层。 如果插入的子图层已经在另一个图层层次结构中,则首先将其从该层次结构中移除。 |
使用您自己创建的图层对象时,可以使用上述方法。
您不会使用这些方法来排列属于图层支持视图的图层。
但是,图层支持视图可以充当您自己创建的独立图层的父级。
子图层的定位和大小
添加和插入子图层时,您必须先设置子图层的大小和位置,然后子图层才会显示在屏幕上。
您可以在将子图层添加到图层层次结构后修改其大小和位置,但应养成在创建图层时设置这些值的习惯。
您可以使用 bounds
属性设置子图层的大小,并使用 position
属性设置其在超图层中的位置。
边界矩形的原点几乎始终是 (0, 0),大小是您希望图层以点为单位指定的任意大小。
position
属性中的值是相对于图层的锚点解释的,默认情况下,锚点位于图层的中心。
如果您没有为这些属性分配值,Core Animation 会将图层的初始宽度和高度设置为 0,并将位置设置为 (0, 0)。
myLayer.bounds = CGRectMake(0, 0, 100, 100);
myLayer.position = CGPointMake(200, 200);
重要提示: 始终使用整数作为图层的宽度和高度。
图层层次如何影响动画
某些超图层属性会影响应用于其子图层的任何动画的行为。
其中一个属性是 speed
属性,它是动画速度的乘数。
此属性的值默认设置为1.0
, 但将其更改为2.0
会导致动画以其原始速度的两倍运行,从而在一半的时间内完成。
此属性不仅影响设置它的图层,还影响该图层的子图层。
这种变化也是乘性的。
如果子图层及其超图层的速度均为2.0
,则子图层上的动画将以其原始速度的四倍运行。
大多数其他图层更改都会以可预测的方式影响其所包含的任何子图层。
例如,对图层应用旋转变换会旋转该图层及其所有子图层。
同样,更改图层的不透明度会更改其子图层的不透明度。
对图层大小的更改遵循调整图层层次结构布局中所述的布局规则。
2、调整图层层次的布局
Core Animation 支持多种选项,用于调整子层的大小和位置以响应其父层的变化。
在 iOS 中,层支持视图的广泛使用使得创建层层次结构变得不那么重要;仅支持手动布局更新。
对于 OS X,还有其他几种选项可用,可让您更轻松地管理层层次结构。
仅当您使用自己创建的独立图层对象构建图层层次结构时,图层级布局才有意义。
如果您的应用图层都与视图相关联,请使用基于视图的布局支持来更新视图的大小和位置以响应更改。
在 OS X 中使用约束来管理图层层次结构
约束允许您使用图层与其父图层或同级图层之间的一组详细关系来指定图层的位置和大小。
定义约束需要以下步骤:
- 创建一个或多个
CAConstraint
对象。使用这些对象来定义约束参数。 - 将约束对象添加到其属性被修改的图层。
- 检索共享
CAConstraintLayoutManager
对象并分配给直接超层。
图 4-1显示了可用于定义约束的属性以及它们影响的图层方面。
您可以使用约束根据图层中点边缘相对于另一图层的位置来更改图层的位置。
您还可以使用它们来更改图层的大小。
您所做的更改可以与超图层成比例,也可以相对于另一图层成比例。
您甚至可以为结果更改添加缩放因子或常数。
这种额外的灵活性使得使用一组简单的规则非常精确地控制图层的大小和位置成为可能。
图 4-1 约束布局管理器属性
每个约束对象都封装了沿同一轴的两个图层之间的一种几何关系。
每个轴最多可以分配两个约束对象,这两个约束决定了哪个属性是可更改的。
例如,如果您为图层的左边缘和右边缘指定约束,则图层的大小会发生变化。
如果您为图层的左边缘和宽度指定约束,则图层的右边缘的位置会发生变化。
如果您为图层的其中一个边缘指定单个约束,Core Animation 会创建一个隐式约束,使图层的大小保持在给定维度上。
创建约束时,必须始终指定三条信息:
- 想要约束的图层方面
- 用作参考的图层
- 比较中使用的参考图层的方面
例 4-1显示了一个简单的约束,它将层的垂直中点固定到其超层的垂直中点。
引用超层时,使用字符串superlayer
。
此字符串是为引用超层而保留的特殊名称。
使用它就无需指向该层或知道该层的名称。
它还允许您更改超层并将约束自动应用于新的父层。
(创建相对于同级层的约束时,必须使用其name
属性来识别同级层。)
例 4-1 定义一个简单的约束
[myLayer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidY
relativeTo:@"superlayer"
attribute:kCAConstraintMidY]];
要在运行时应用约束,您必须将共享CAConstraintLayoutManager
对象附加到直接的超层。
每个层负责管理其子层的布局。
将布局管理器分配给父层会告诉 Core Animation 应用其子层定义的约束。
布局管理器对象会自动应用约束。
将其分配给父层后,您不必告诉它更新布局。
要了解约束在更具体场景中的工作方式,请考虑图 4-2。
在此示例中,设计要求 layerA
的宽度和高度保持不变,并且layerA
保持在超层内的中心。
此外,layerB
的宽度 必须与layerA
的宽度相匹配,layerB
的顶部边缘 必须保持在 layerA
的底部边缘以下 10 个点,并且 layerB
的底部边缘必须保持在超层底部边缘以上 10 个点。
例 4-2显示了用于创建此示例的子层和约束的代码。
图 4-2 基于约束的布局示例
例 4-2 设置图层的约束
// Create and set a constraint layout manager for the parent layer.
theLayer.layoutManager=[CAConstraintLayoutManager layoutManager];
// Create the first sublayer.
CALayer *layerA = [CALayer layer];
layerA.name = @"layerA";
layerA.bounds = CGRectMake(0.0,0.0,100.0,25.0);
layerA.borderWidth = 2.0;
// Keep layerA centered by pinning its midpoint to its parent's midpoint.
[layerA addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidY
relativeTo:@"superlayer"
attribute:kCAConstraintMidY]];
[layerA addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidX
relativeTo:@"superlayer"
attribute:kCAConstraintMidX]];
[theLayer addSublayer:layerA];
// Create the second sublayer
CALayer *layerB = [CALayer layer];
layerB.name = @"layerB";
layerB.borderWidth = 2.0;
// Make the width of layerB match the width of layerA.
[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintWidth
relativeTo:@"layerA"
attribute:kCAConstraintWidth]];
// Make the horizontal midpoint of layerB match that of layerA
[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidX
relativeTo:@"layerA"
attribute:kCAConstraintMidX]];
// Position the top edge of layerB 10 points from the bottom edge of layerA.
[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxY
relativeTo:@"layerA"
attribute:kCAConstraintMinY
offset:-10.0]];
// Position the bottom edge of layerB 10 points
// from the bottom edge of the parent layer.
[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinY
relativeTo:@"superlayer"
attribute:kCAConstraintMinY
offset:+10.0]];
[theLayer addSublayer:layerB];
例 4-2中值得注意的一点是,代码从未明确设置 layerB
的大小。
由于定义的约束,每次更新布局时都会自动设置 layerB
的宽度和高度。
因此,无需使用边界矩形设置大小。
警告: 创建约束时,请勿在约束之间创建循环引用。
循环约束会导致无法计算所需的布局信息。
遇到此类循环引用时,布局行为将无法定义。
为你的 OS X 图层层次设置自动调整规则
自动调整大小规则是 OS X 中调整图层大小和位置的另一种方法。
使用自动调整大小规则,您可以指定图层的边缘与超图层的相应边缘之间的距离是固定的还是可变的。
您可以类似地指定图层的宽度或高度是固定的还是可变的。
关系始终存在于图层与其超图层之间。
您不能使用自动调整大小规则来指定同级图层之间的关系。
要设置图层的自动调整大小规则,您必须为图层的autoresizingMask
属性分配适当的常量。
默认情况下,图层配置为具有固定的宽度和高度。
在布局期间,Core Animation 会自动为您计算图层的精确大小和位置,并根据许多因素进行一组复杂的计算。
Core Animation 在要求您的代理进行任何手动布局更新之前应用自动调整大小行为,因此您可以根据需要使用代理来调整自动调整大小布局的结果。
手动布局图层层次
在 iOS 和 OS X 上,您可以通过在 superlayer 的代理对象上实现layoutSublayersOfLayer:
方法来手动处理布局。
您可以使用该方法调整当前嵌入在 layer 内的任何子layer的大小和位置。
在进行手动布局更新时,您需要执行必要的计算来定位每个子layer。
如果您正在实现自定义图层子类,则您的子类可以重写 layoutSublayers
方法并使用该方法(而不是代理)来处理任何布局任务。
您应该只在需要完全控制自定义图层类中子图层的位置的情况下重写此方法。
替换默认实现可防止 Core Animation 在 OS X 上应用约束或自动调整大小规则。
3、子图层和裁剪
与视图不同,超级图层不会自动剪裁位于其边界矩形之外的子图层的内容。
相反,超级图层默认允许其子图层完整显示。
但是,您可以通过将图层的masksToBounds
属性设置为YES
来重新启用剪裁。
如果指定了圆角半径,图层的剪贴蒙版形状将包括图层的圆角半径。
图 4-3显示了masksToBounds
属性如何影响圆角图层的图层。
当属性设置为NO
时,子图层将完整显示,即使它们超出了父图层的边界。
将属性更改为YES
会导致其内容被剪贴。
图 4-3 根据父级边界裁剪子图层
4、在图层之间转换坐标值
有时,您可能需要将一个图层中的坐标值转换为不同图层中同一屏幕位置的坐标值。
该类CALayer
提供了一组简单的转换例程,可用于此目的:
convertPoint:fromLayer:
convertPoint:toLayer:
convertRect:fromLayer:
convertRect:toLayer:
除了转换点和矩形值之外,您还可以使用convertTime:fromLayer:
和convertTime:toLayer:
方法在图层之间转换时间值。
每个图层都定义自己的本地时间空间,并使用该时间空间将动画的开始和结束与系统的其余部分同步。
默认情况下,这些时间空间是同步的;但是,如果您更改一组图层的动画速度,则这些图层的时间空间也会相应更改。
您可以使用时间转换方法来考虑任何此类因素,并确保两个图层的时间同步。
六、高级动画技巧
有多种方法可以配置基于属性的动画或关键帧动画,以便为您做更多事情。
需要同时或按顺序执行多个动画的应用可以使用更高级的行为来同步这些动画的时间或将它们链接在一起。
您还可以使用其他类型的动画对象来创建视觉过渡和其他有趣的动画效果。
1、过渡动画支持图层可见性的更改
顾名思义,过渡动画对象会为图层创建动画视觉过渡。
过渡对象最常见的用途是以协调的方式为一个图层的出现和另一个图层的消失制作动画。
与基于属性的动画(动画会更改图层的一个属性)不同,过渡动画会操纵图层的缓存图像来创建视觉效果,而这些效果很难或不可能仅通过更改属性来实现。
标准类型的过渡允许您执行显示、推动、移动或交叉淡入淡出动画。
在 OS X 上,您还可以使用 Core Image 滤镜来创建使用其他类型效果(如擦除、页面卷曲、波纹或您设计的自定义效果)的过渡。
要执行过渡动画,您需要创建一个CATransition
对象并将其添加到过渡所涉及的图层中。
您可以使用过渡对象来指定要执行的过渡类型以及过渡动画的起点和终点。
您也不需要使用整个过渡动画。
过渡对象允许您指定动画制作时要使用的开始和结束进度值。
这些值允许您执行诸如在中间点开始或结束动画之类的操作。
例 5-1显示了用于在两个视图之间创建动画推送过渡的代码。
在示例中,myView1
和myView2
都位于 同一父视图中的同一位置,但当前只有myView1
可见。
推送过渡导致myView1
向左滑出并淡出直至隐藏,而myView2
从右侧滑入并变为可见。
更新两个视图的隐藏属性可确保动画结束时两个视图的可见性都是正确的。
例 5-1 iOS 中两个视图之间的过渡动画
CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
transition.duration = 1.0;
// Add the transition animation to both layers
[myView1.layer addAnimation:transition forKey:@"transition"];
[myView2.layer addAnimation:transition forKey:@"transition"];
// Finally, change the visibility of the layers.
myView1.hidden = YES;
myView2.hidden = NO;
当两个层涉及同一个过渡时,您可以对它们使用同一个过渡对象。
使用同一个过渡对象还可以简化您必须编写的代码。
但是,您可以使用不同的过渡对象,如果每个层的过渡参数不同,则肯定需要这样做。
例 5-2展示了如何使用 Core Image 滤镜在 OS X 上实现过渡效果。
在为滤镜配置所需的参数后,将其分配给过渡对象的filter
属性。
之后,应用动画的过程与其他类型的动画对象相同。
例 5-2 使用 Core Image 滤镜在 OS X 上制作过渡动画
// Create the Core Image filter, setting several key parameters.
CIFilter* aFilter = [CIFilter filterWithName:@"CIBarsSwipeTransition"];
[aFilter setValue:[NSNumber numberWithFloat:3.14] forKey:@"inputAngle"];
[aFilter setValue:[NSNumber numberWithFloat:30.0] forKey:@"inputWidth"];
[aFilter setValue:[NSNumber numberWithFloat:10.0] forKey:@"inputBarOffset"];
// Create the transition object
CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.filter = aFilter;
transition.duration = 1.0;
[self.imageView2 setHidden:NO];
[self.imageView.layer addAnimation:transition forKey:@"transition"];
[self.imageView2.layer addAnimation:transition forKey:@"transition"];
[self.imageView setHidden:YES];
注意: 在动画中使用 Core Image 滤镜时,最棘手的部分是配置滤镜。
例如,对于条形滑动过渡,如果指定输入角度过高或过低,可能会让过渡看起来好像没有发生。
如果您没有看到预期的动画,请尝试将滤镜参数调整为不同的值,看看结果是否会发生变化。
2、自定义动画的时间
计时是动画的重要组成部分,使用 Core Animation,您可以通过CAMediaTiming
协议的方法和属性为动画指定精确的计时信息。
两个 Core Animation 类采用了此协议。
CAAnimation
类采用了此协议,以便您可以在动画对象中指定计时信息。
CALayer
类也采用了此协议,以便您可以为隐式动画配置一些与计时相关的功能,尽管包装这些动画的隐式事务对象通常会提供优先的默认计时信息。
在考虑时间和动画时,了解图层对象如何与时间一起工作非常重要。
每个图层都有自己的本地时间,用于管理动画时间。
通常,两个不同图层的本地时间非常接近,您可以为每个图层指定相同的时间值,而用户可能不会注意到任何事情。
但是,图层的本地时间可以通过其父图层或其自己的计时参数进行修改。
例如,更改图层的speed
属性会导致该图层(及其子图层)上的动画持续时间按比例更改。
为了帮助您确保时间值适合给定的图层,该类CALayer
定义了convertTime:fromLayer:
和convertTime:toLayer:
方法。
您可以使用这些方法将固定时间值转换为图层的本地时间,或将时间值从一个图层转换为另一个图层。
这些方法考虑了可能影响图层本地时间的媒体计时属性,并返回一个可用于其他图层的值。
例 5-3显示了您应该定期使用它来获取图层的当前本地时间的示例。
CACurrentMediaTime
函数是一个便利函数,它返回计算机的当前时钟时间,该方法获取该时间并将其转换为图层的本地时间。
例 5-3 获取图层的当前本地时间
CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];
一旦获得了图层本地时间的时间值,您就可以使用该值来更新动画对象或图层的时序相关属性。
借助这些时序属性,您可以实现一些有趣的动画行为,包括:
- 使用
beginTime
属性设置动画的开始时间。
通常,动画在下一个更新周期开始。
您可以使用beginTime
参数将动画开始时间延迟几秒钟。
将两个动画链接在一起的方法是将一个动画的开始时间设置为与另一个动画的结束时间匹配。
如果您延迟动画的开始,您可能还想将fillMode
属性设置为kCAFillModeBackwards
。
此填充模式会导致图层显示动画的起始值,即使图层树中的图层对象包含不同的值。
如果没有此填充模式,您将在动画开始执行之前看到跳转到最终值。
还有其他填充模式可用。 autoreverses
属性 使动画执行指定的持续时间,然后返回到动画的起始值。
您可以将此属性与repeatCount
属性组合,在起始值和结束值之间来回动画。
将自动反转动画的重复次数设置为整数(例如 1.0)会导致动画在其起始值处停止。
添加额外的半步(例如重复次数为 1.5)会导致动画在其结束值处停止。- 使用具有组动画的
timeOffset
属性可以使某些动画比其他动画晚一些开始。
3、暂停和恢复动画
要暂停动画,您可以使用图层采用CAMediaTiming
协议的事实,并将图层动画的速度设置为0.0
。
将速度设置为零会暂停动画,直到您将该值改回非零值。
例 5-4展示了一个简单示例,说明如何暂停和稍后恢复动画。
例 5-4 暂停和恢复图层的动画
-(void)pauseLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
-(void)resumeLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}
4、显式事务让您可以更改动画参数
您对图层所做的每项更改都必须是事务的一部分。
CATransaction
类 负责管理动画的创建和分组,以及在适当时间执行动画。
在大多数情况下,您无需创建自己的事务。
每当您向某个图层添加显式或隐式动画时,Core Animation 都会自动创建隐式事务。
但是,您也可以创建显式事务来更精确地管理这些动画。
您可以使用CATransaction
类的方法创建和管理事务。
要启动(并隐式创建)新事务,请调用begin
类方法;要结束该事务,请调用commit
类方法。
在这些调用之间是您希望成为事务一部分的更改。
例如,要更改一个层的两个属性,您可以使用例 5-5中的代码。
例 5-5 创建显式事务
[CATransaction begin];
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];
使用事务的主要原因之一是,在显式事务的范围内,您可以更改持续时间、计时函数和其他参数。
您还可以为整个事务分配一个完成块,以便在动画组完成时通知您的应用。
更改动画参数需要使用 setValue:forKey:
方法修改事务字典中的相应键。
例如,要将默认持续时间更改为 10 秒,您需要更改kCATransactionAnimationDuration
键,如例 5-6所示。
例 5-6 更改动画的默认持续时间
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
forKey:kCATransactionAnimationDuration];
// Perform the animations
[CATransaction commit];
如果您想要为不同的动画集提供不同的默认值,则可以嵌套事务。
要将一个事务嵌套在另一个事务中,只需再次调用类方法begin
即可。
每次begin
调用都必须与对该方法的相应调用相匹配commit
。
只有在您提交最外层事务的更改后,Core Animation 才会开始相关动画。
例 5-7展示了一个事务嵌套在另一个事务中的例子。
在这个例子中,内部事务更改了与外部事务相同的动画参数,但使用了不同的值。
例 5-7 嵌套显式事务
[CATransaction begin]; // Outer transaction
// Change the animation duration to two seconds
[CATransaction setValue:[NSNumber numberWithFloat:2.0f]
forKey:kCATransactionAnimationDuration];
// Move the layer to a new position
theLayer.position = CGPointMake(0.0,0.0);
[CATransaction begin]; // Inner transaction
// Change the animation duration to five seconds
[CATransaction setValue:[NSNumber numberWithFloat:5.0f]
forKey:kCATransactionAnimationDuration];
// Change the zPosition and opacity
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit]; // Inner transaction
[CATransaction commit]; // Outer transaction
5、为动画添加透视效果
应用可以在三维空间中操作图层,但为了简单起见,Core Animation 使用平行投影显示图层,这实际上将场景展平为二维平面。
此默认行为会导致具有不同zPosition
值 的相同大小的图层显示为相同大小,即使它们在 z 轴上相距很远。
您通常在三维中查看此类场景的视角消失了。
但是,您可以通过修改图层的变换矩阵以包含透视信息来更改该行为。
修改场景的透视时,您需要修改包含正在查看的图层的超图层的sublayerTransform
矩阵。
修改超图层通过将相同的透视信息应用于所有子图层,可以简化您必须编写的代码。
它还可以确保将透视正确应用于在不同平面上相互重叠的同级子图层。
例 5-8显示了为父层创建简单透视变换的方法。
在本例中,自定义eyePosition
变量指定沿 z 轴查看层的相对距离。
通常,您会为eyePosition
指定一个正值 使层保持按预期方向定向。
较大的值会导致场景更平坦,而较小的值会导致层之间的视觉差异更大。
例 5-8 向父层添加透视变换
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0/eyePosition;
// Apply the transform to a parent layer.
myParentLayer.sublayerTransform = perspective;
配置父层后,您可以更改任何子层的zPosition
属性,并观察它们的大小如何根据它们与眼睛位置的相对距离而变化。
七、改变图层的默认行为
Core Animation 使用操作对象为图层实现隐式动画行为。
操作对象是符合CAAction
协议并定义要在图层上执行的一些相关行为的对象。
所有CAAnimation
对象都实现该协议,并且这些对象通常被指定为在图层属性发生变化时执行。
动画属性是一种操作,但您可以定义几乎任何您想要的行为。
不过,要做到这一点,您必须定义操作对象并将其与应用的图层对象关联起来。
1、自定义动作对象采用 CAAction 协议
要创建您自己的操作对象,您其中一个类 采用CAAction
协议,并实现 runActionForKey:object:arguments:
方法。
在该方法中,使用可用信息执行您想要对图层执行的任何操作。
您可以使用该方法将动画对象添加到图层,也可以使用它来执行其他任务。
定义操作对象时,您必须决定如何触发该操作。
操作的触发器定义了您稍后注册该操作时使用的键。
以下任一情况都可触发操作对象:
- 图层的某个属性的值已更改。
这可以是图层的任何属性,而不仅仅是可动画的属性。
(您还可以将操作与您添加到图层的自定义属性相关联。)标识此操作的键是属性的名称。 - 图层变为可见或被添加到图层层次结构中。
标识此操作的键是kCAOnOrderIn
。 - 该图层已从图层层次结构中移除。
标识此操作的键是kCAOnOrderOut
。 - 该图层即将参与过渡动画。
标识此操作的键是kCATransition
。
2、操作对象必须安装在图层上才能产生效果
在执行操作之前,图层需要找到要执行的相应操作对象。
与图层相关的操作的键是要修改的属性的名称,或标识该操作的特殊字符串。
当图层上发生相应事件时,图层会调用其actionForKey:
方法来搜索与该键关联的操作对象。
您的应用可以在此搜索过程中的几个点进行干预,并为该键提供相关的操作对象。
Core Animation 按以下顺序查找动作对象:
- 如果该层具有代理,并且该代理实现了
actionForLayer:forKey:
方法,则该层将调用该方法。
代理必须执行以下操作之一:- 返回给定键的操作对象。
- 如果不处理该操作则返回
nil
,在这种情况下搜索继续。 - 返回
NSNull
对象,在这种情况下搜索立即结束。
- 该层在其
actions
字典中查找给定的键。 - 该层在
style
字典中查找包含该键的操作字典。
(换句话说,style
字典包含一个actions
键,该键的值也是一个字典。
该层在第二个字典中查找给定的键。) - 该层调用其
defaultActionForKey:
类方法。 - 该图层执行由核心动画定义的隐式动作(如果有)。
如果您在任何适当的搜索点提供操作对象,则图层将停止搜索并执行返回的操作对象。
当找到操作对象时,图层将调用该对象的runActionForKey:object:arguments:
方法来执行操作。
如果您为给定键定义的操作已经是CAAnimation
类的实例,则可以使用该方法的默认实现来执行动画。
如果您要定义自己的符合CAAction
协议的自定义对象,则必须使用对象对该方法的实现来采取适当的操作。
安装操作对象的位置取决于您打算如何修改图层。
- 对于您可能仅在特定情况下应用的操作,或者对于已经使用代理对象的层,请提供代理并实现其
actionForLayer:forKey:
方法。 - 对于通常不使用代理的层对象,将操作添加到层的
actions
字典中。 - 对于与您在图层对象上定义的自定义属性相关的操作,请将该操作包含在图层的
style
字典中。 - 对于对于层的行为至关重要的操作,请对层进行子类化并重写方法
defaultActionForKey:
。
例 6-1展示了用于提供操作对象的代理方法的实现。
在本例中,代理会查找图层contents
属性的更改,并使用过渡动画将新内容交换到位。
例 6-1 使用层代理对象提供操作
- (id<CAAction>)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:kCAMediaTimingFunctionEaseIn];
theAnimation.type = kCATransitionPush;
theAnimation.subtype = kCATransitionFromRight;
}
return theAnimation;
}
3、使用 CATransaction 类暂时禁用操作
您可以使用CATransaction
类暂时禁用图层操作。
当您更改图层的属性时,Core Animation 通常会创建一个隐式事务对象来为更改设置动画。
如果您不想为更改设置动画,可以通过创建显式事务并将其kCATransactionDisableActions
属性设置为true
来禁用隐式动画。
例 6-2显示了从图层树中删除指定图层时禁用动画的代码片段。
例 6-2 暂时禁用图层的操作
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
forKey:kCATransactionDisableActions];
[aLayer removeFromSuperlayer];
[CATransaction commit];
有关使用事务对象管理动画行为的更多信息,请参阅 显式事务让您更改动画参数。
八、提高动画性能
Core Animation 是提高应用动画帧速率的好方法,但使用 Core Animation 并不能保证性能的提升。
尤其是在 OS X 中,您仍然必须选择最有效的 Core Animation 行为方式。
与所有与性能相关的问题一样,您应该使用 Instruments 来测量和跟踪应用随时间的性能,以确保性能不断提高而不是倒退。
1、为你的 OS X 视图选择最佳的重绘策略
即使视图是层支持的,NSView
类的默认重绘策略 也会保留该类的原始绘制行为。
如果您在应用中使用层支持的视图,则应检查重绘策略选项,并选择为您的应用提供最佳性能的策略。
在大多数情况下,默认策略并不是最有可能提供最佳性能的策略。
相反, NSViewLayerContentsRedrawOnSetNeedsDisplay
策略更有可能减少应用的绘制量并提高性能。
其他策略也可能为特定类型的视图提供更好的性能。
有关视图重绘策略的更多信息,请参阅 OS X 视图的图层重绘策略对性能的影响。
2、更新 OS X 中的图层以优化渲染路径
在 OS X v10.8 及更高版本中,视图有两种更新底层图层内容的选项。
在 OS X v10.7 及更早版本中更新图层支持的视图时,图层会将视图drawRect:
方法中的绘图命令 捕获到支持位图图像中。
缓存绘图命令很有效,但并非在所有情况下都是最有效的选项。
如果您知道如何直接提供图层的内容而不实际渲染它们,则可以使用 updateLayer
方法来实现。
有关渲染的不同路径的信息(包括涉及方法的路径)updateLayer
,请参阅 使用代理提供图层的内容。
3、常规提示和技巧
有多种方法可以提高层实现的效率。
不过,与任何此类优化一样,在尝试优化之前,您应始终测量代码的当前性能。
这为您提供了一个基准,您可以用它来确定优化是否有效。
尽可能使用不透明层
将图层的 opaque
属性设置为YES
,可让 Core Animation 知道它不需要为图层维护 Alpha 通道。
没有 Alpha 通道意味着合成器不需要将图层的内容与其背景内容混合,从而节省渲染时间。
但是,此属性主要适用于属于图层支持视图的图层或 Core Animation 创建底层图层位图的情况。
如果将图像直接分配给图层的contents
属性,则无论opaque
属性中的值如何,该图像的 Alpha 通道都会保留。
对 CAShapeLayer 对象使用更简单的路径
CAShapeLayer
类 通过在合成时将您提供的路径渲染为位图图像来创建其内容。
优点是图层始终以最佳分辨率绘制路径,但这种优点是以额外的渲染时间为代价的。
如果您提供的路径很复杂,则光栅化该路径可能会变得过于昂贵。
如果图层的大小经常更改(因此必须经常重新绘制),则绘制所花费的时间可能会累积起来并成为性能瓶颈。
减少形状图层绘制时间的一种方法是将复杂形状分解为更简单的形状。
使用更简单的路径并在合成器中将多个CAShapeLayer
对象层叠在一起比绘制一条大型复杂路径要快得多。
这是因为绘制操作发生在 CPU 上,而合成发生在 GPU 上。
不过,与任何此类简化一样,潜在的性能提升取决于您的内容。
因此,在优化之前测量代码的性能尤为重要,这样您就有了一个可用于比较的基准。
为相同图层明确设置图层内容
如果您在多个图层对象中使用同一图像,请自行加载图像并将其直接分配给这些图层对象的contents
属性。
将图像分配给contents
属性可防止图层为后备存储分配内存。
相反,图层会使用您提供的图像作为其后备存储。
当多个图层使用同一图像时,这意味着所有这些图层共享相同的内存,而不是为自己分配图像的副本。
始终将图层大小设置为整数值
为了获得最佳效果,请始终将图层对象的宽度和高度设置为整数值。
虽然您使用浮点数指定图层边界的宽度和高度,但图层边界最终用于创建位图图像。
为宽度和高度指定整数值可简化 Core Animation 必须执行的工作,以创建和管理后备存储和其他图层信息。
根据需要使用异步图层渲染
您代理的drawLayer:inContext:
方法或视图的drawRect:
方法中,执行的任何绘制 通常都会在应用的主线程上同步发生。
但在某些情况下,同步绘制内容可能无法提供最佳性能。
如果您注意到动画效果不佳,您可以尝试启用图层上的drawsAsynchronously
属性以将这些操作移至后台线程。
如果这样做,请确保您的绘制代码是线程安全的。
与往常一样,在将其放入生产代码之前,您应该始终测量异步绘制的性能。
向图层添加阴影时指定阴影路径
让 Core Animation 确定阴影的形状可能代价高昂,并且会影响应用的性能。
与其让 Core Animation 确定阴影的形状,不如使用 CALayer
的shadowPath
属性明确指定阴影形状。
当您为此属性指定路径对象时,Core Animation 会使用该形状来绘制和缓存阴影效果。
对于形状从不改变或很少改变的图层,这可以减少 Core Animation 完成的渲染量,从而大大提高性能。
九、图层样式属性动画
在渲染过程中,Core Animation 会取图层的不同属性,并按照特定的顺序进行渲染。
这个顺序决定了图层的最终外观。
本章演示了通过设置不同的图层样式属性所实现的渲染效果。
注意: Mac OS X 和 iOS 上可用的图层样式属性有所不同,并在本章中进行了注明。
1、几何特性
图层的几何属性指定了它相对于父图层的显示方式。
几何属性还指定了用于圆化图层角的半径以及应用于图层及其子图层的变换。
图 A-1显示了示例图层的边界矩形。
图 A-1 层几何形状
以下CALayer
属性指定图层的几何形状:
bounds
position
frame
(根据bounds
和计算得出position
,并且不可制作动画)anchorPoint
cornerRadius
transform
zPosition
iOS 注意事项: cornerRadius
属性仅在 iOS 3.0 及更高版本中受支持。
2、背景属性
Core Animation 渲染的第一件事是图层的背景。
您可以为背景指定颜色。
在 OS X 中,您还可以指定要应用于背景内容的 Core Image 滤镜。
图 A-2显示了示例图层的两个版本。
左侧的图层已设置其backgroundColor
属性,而右侧的图层没有背景颜色,但有边框和一些内容,并且为其backgroundFilters
属性分配了捏合失真滤镜。
图 A-2 具有背景颜色的图层
背景滤镜应用于图层后面的内容,主要由父图层的内容组成。
您可以使用背景滤镜使前景图层内容突出;例如,通过应用模糊滤镜。
以下CALayer
属性会影响图层背景的显示:
backgroundColor
backgroundFilters
(iOS 不支持)
平台注意事项: 在 iOS 中, backgroundFilters
属性在CALayer
类中公开,但您分配给该属性的过滤器将被忽略。
3、图层内容
如果图层有任何内容,则该内容将呈现在背景颜色之上。
您可以通过直接设置位图、使用代理指定内容或通过子类化图层并直接绘制内容来提供图层内容。
并且您可以使用许多不同的绘图技术(包括 Quartz、Metal、OpenGL 和 Quartz Composer)来提供该内容。
图 A-3显示了一个示例图层,其内容是直接设置的位图。
位图内容由一个大部分透明的空间组成,右下角有 Automator 图标。
图 A-3 显示位图图像的图层
具有角半径的图层不会自动剪切其内容;但是,将图层的masksToBounds
属性设置为YES
确实会导致图层剪切到其角半径。
以下CALayer
属性会影响图层内容的显示:
contents
contentsGravity
masksToBounds
4、子层内容
任何图层都可能包含一个或多个子图层,称为子图层。
子图层以递归方式渲染,并相对于父图层的边界矩形进行定位。
此外,Core Animation 将父图层的sublayerTransform
锚点应用于每个子图层。
您可以使用子图层变换将透视和其他效果均匀地应用于所有图层。
图 A-4显示了具有两个子图层的示例图层。
左侧的版本包含背景颜色,而右侧的版本则没有。
图 A-4 图层显示子图层内容
设置masksToBounds
图层的属性会YES
,导致所有子图层都被剪裁到图层的边界内。
以下CALayer
属性会影响图层子图层的显示:
sublayers
masksToBounds
sublayerTransform
5、边框属性
图层可以使用指定的颜色和宽度显示可选边框。
边框遵循图层的边界矩形,并考虑任何角半径值。
图 A-5显示了应用边框后的示例图层。
请注意,图层边界之外的内容和子图层在边框下方呈现。
图A-5 显示边框属性内容的图层
以下CALayer
属性会影响图层边框的显示:
borderColor
borderWidth
平台注意事项: borderColor
和 borderWidth
属性仅在 iOS 3.0 及更高版本中受支持。
6、过滤器属性
在 OS X 中,您可以对图层的内容应用一个或多个滤镜,并使用自定义合成滤镜来指定图层的内容如何与其底层图层的内容混合。
图 A-6显示了应用了 Core Image 色调分离滤镜的示例图层。
图 A-6 显示过滤器属性的图层
以下CALayer
属性指定图层内容过滤器:
filters
compositingFilter
平台注意事项: 在 iOS 中,图层会忽略您为其指定的任何过滤器。
7、阴影属性
图层可以显示阴影效果并配置其形状、不透明度、颜色、偏移量和模糊半径。
如果未指定自定义阴影形状,则阴影基于图层中不完全透明的部分。
图 A-7显示了同一示例图层的几个不同版本,其中应用了红色阴影。
左侧和中间版本包含背景色,因此阴影仅出现在图层边框周围。
但是,右侧版本不包含背景色。
在这种情况下,阴影应用于图层的内容、边框和子图层。
图 A-7 显示阴影属性的图层
以下CALayer
属性会影响图层阴影的显示:
shadowColor
shadowOffset
shadowOpacity
shadowRadius
shadowPath
平台注意事项: iOS 3.2 及更高版本支持shadowColor
、shadowOffset
、shadowOpacity
和shadowRadius
属性。
iOS 3.2 及更高版本以及 OS X v10.7 及更高版本支持 shadowPath
属性 。
8、不透明度属性
图层的不透明度属性决定了有多少背景内容可以透过图层显示出来。
图 A-8显示了一个不透明度设置为 的示例图层0.5
。
这允许背景图像的某些部分透过图层显示出来。
图 A-8 包含不透明度属性的图层
以下CALayer
属性指定图层的不透明度:
opacity
9、蒙版属性
您可以使用遮罩来遮挡图层的全部或部分内容。
遮罩本身是一个图层对象,其 alpha 通道用于确定哪些内容被阻挡以及哪些内容被传输。
遮罩图层内容的不透明部分允许底层图层内容显示出来,而透明部分则部分或全部遮挡底层内容。
图 A-9显示了由遮罩图层和两个不同背景合成的示例图层。
在左侧版本中,图层的不透明度设置为 1.0。
在右侧版本中,图层的不透明度设置为 0.5,这增加了通过图层的遮罩部分传输的背景内容量。
图 A-9 与 mask 属性合成的图层
以下CALayer
属性指定图层的蒙版:
mask
十、可动画的属性
CALayer
和CIFilter
中的许多属性都可以进行动画处理。
本附录列出了这些属性以及默认使用的动画。
1、CALayer 动画属性
表 B-1列出了您可能考虑设置动画的CALayer
类的属性。
对于每个属性,该表还列出了为执行隐式动画而创建的默认动画对象的类型。
Table B-1 Layer properties and their default animations
财产 | 默认动画 |
---|---|
anchorPoint | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
backgroundColor | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
backgroundFilters | 使用默认隐含CATransition 对象,如表 B-3中所述。滤镜的子属性使用默认隐含 CABasicAnimation 对象进行动画处理,如 表 B-2中所述。 |
borderColor | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
borderWidth | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
bounds | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
compositingFilter | 使用默认隐含CATransition 对象,如表 B-3中所述。滤镜的子属性使用默认隐含 CABasicAnimation 对象进行动画处理,如表 B-2中所述。 |
contents | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
contentsRect | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
cornerRadius | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
doubleSided | 没有默认的隐含动画。 |
filters | 使用默认隐含CABasicAnimation 对象,如表 B-2中所述。滤镜的子属性使用默认隐含 CABasicAnimation 对象进行动画处理,如 表 B-2中所述。 |
frame | 此属性不可设置动画。 您可以通过设置 bounds 和position 属性的动画来实现相同的结果。 |
hidden | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
mask | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
masksToBounds | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
opacity | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
position | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
shadowColor | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
shadowOffset | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
shadowOpacity | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
shadowPath | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
shadowRadius | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
sublayers | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
sublayerTransform | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
transform | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
zPosition | 使用默认隐含对象,如表 B-2CABasicAnimation 所述。 |
表 B-2列出了默认基于属性的动画的动画属性。
Table B-2 Default Implied Basic Animation
描述 | 价值 |
---|---|
班级 | CABasicAnimation |
期间 | 0.25 秒,或当前交易的持续时间 |
密钥路径 | 设置为图层的属性名称。 |
表 B-3列出了默认基于过渡的动画的动画对象配置。
Table B-3 Default Implied Transition
描述 | 价值 |
---|---|
班级 | CATransition |
期间 | 0.25 秒,或当前交易的持续时间 |
类型 | 褪色 (kCATransitionFade ) |
开始进度 | 0.0 |
结束进度 | 1.0 |
2、CIFilter 动画属性
Core Animation 向 Core Image 的CIFilter
类添加了以下可动画属性。
这些属性仅在 OS X 上可用。
name
enabled
有关这些附加功能的更多信息,请参阅 CIFilter 核心动画附加功能。
十一、键值编码扩展
Core Animation 扩展了NSKeyValueCoding
协议,CALayer
和CAAnimation
类 相关的协议。
此扩展为某些键添加了默认值,扩展了包装约定,并为CGPoint
、CGRect
、CGSize
和CATransform3D
类型添加了键路径支持。
1、符合键值编码的容器类
CAAnimation
和CALayer
类是符合键值编码的容器类,这意味着您可以为任意键设置值。
即使someKey
键 不是CALayer
类的声明属性,您仍然可以为其设置值,如下所示:
[theLayer setValue:[NSNumber numberWithInteger:50] forKey:@"someKey"];
您还可以检索任意键的值,就像检索其他键路径的值一样。
例如,要检索之前设置的someKey
路径的值,您可以使用以下代码:
someKeyValue=[theLayer valueForKey:@"someKey"];
OS X 注意: CAAnimation
和 CALayer
类支持NSCoding
协议,它们会自动归档为这些类的实例设置的任何附加键。
2、默认值支持
Core Animation 为键值编码添加了一个约定,即类可以为没有设置值的键提供默认值。
CAAnimation
和CALayer
类使用defaultValueForKey:
类方法支持此约定。
要为键提供默认值,请创建所需类的子类并重写其defaultValueForKey:
方法。
此方法的实现应检查键参数并返回适当的默认值。
列表 C-1 显示了为masksToBounds
属性提供默认值的层对象的defaultValueForKey:
方法 示例实现。
例 C-1 defaultValueForKey 的示例实现:
+ (id)defaultValueForKey:(NSString *)key
{
if ([key isEqualToString:@"masksToBounds"])
return [NSNumber numberWithBool:YES];
return [super defaultValueForKey:key];
}
3、包装约定
当键的数据由标量值或 C 数据结构组成时,您必须先将该类型包装在对象中,然后再将其分配给层。
同样,在访问该类型时,您必须检索一个对象,然后使用相应类的扩展解包相应的值。
表 C-1列出了常用的 C 类型以及用于包装它们的 Objective-C 类。
C 型 | 包装类 |
---|---|
CGPoint | NSValue |
CGSize | NSValue |
CGRect | NSValue |
CATransform3D | NSValue |
CGAffineTransform | NSAffineTransform (仅限 OS X) |
4、结构的关键路径支持
CAAnimation
和CALayer
类可让您使用关键路径访问所选数据结构的字段。
此功能是一种方便的方式来指定要动画的数据结构的字段。
您还可以将这些约定与setValue:forKeyPath:
和 valueForKeyPath:
方法结合使用来设置和获取这些字段。
CATransform3D 关键路径
您可以使用增强的键路径支持来检索包含CATransform3D
数据类型的属性的特定转换值。
要指定图层转换的完整键路径,您可以使用字符串值transform
或sublayerTransform
后跟表 C-2中的字段键路径之一。
例如,要指定围绕图层 z 轴的旋转因子,您可以指定键路径transform.rotation.z
。
Table C-2 Transform field value key paths
字段键路径 | 描述 |
---|---|
rotation.x | 设置为一个NSNumber 对象,其值是 x 轴上的旋转(以弧度为单位)。 |
rotation.y | 设置为一个NSNumber 对象,其值是 y 轴的旋转(以弧度为单位)。 |
rotation.z | 设置为一个NSNumber 对象,其值是 z 轴上的旋转(以弧度为单位)。 |
rotation | 设置为一个NSNumber 对象,其值是 z 轴的旋转(以弧度为单位)。 |
此字段与设置字段相同rotation.z 。 | |
scale.x | 设置为一个NSNumber 对象,其值为 x 轴的比例因子。 |
scale.y | 设置为一个NSNumber 对象,其值为 y 轴的比例因子。 |
scale.z | 设置为一个NSNumber 对象,其值为 z 轴的比例因子。 |
scale | 设置为一个NSNumber 对象,其值是所有三个比例因子的平均值。 |
translation.x | 设置为一个NSNumber 对象,其值为沿 x 轴的平移因子。 |
translation.y | 设置为一个NSNumber 对象,其值为沿 y 轴的平移因子。 |
translation.z | 设置为一个NSNumber 对象,其值为沿 z 轴的平移因子。 |
翻译 | 设置为NSValue 包含NSSize 或CGSize 数据类型的对象。该数据类型表示在 x 和 y 轴上平移的量。 |
以下示例展示了如何使用 setValue:forKeyPath:
方法修改图层。
该示例将 x 轴的平移因子设置为 10 点,从而使图层沿指示的轴移动该量。
[myLayer setValue:[NSNumber numberWithFloat:10.0] forKeyPath:@"transform.translation.x"];
注意: 使用键路径设置值与使用 Objective-C 属性设置值不同。
您不能使用属性表示法来设置转换值。
您必须将 setValue:forKeyPath:
方法与前面的键路径字符串一起使用。
CGPoint 关键路径
如果给定属性的值是CGPoint
数据类型,则可以将表 C-3 中的字段名称之一附加到该属性以获取或设置该值。
例如,要更改图层position
属性的 x 组件,可以写入position.x
键路径。
Table C-3 CGPoint data structure fields
结构字段 | 描述 |
---|---|
x | 该点的 x 分量。 |
y | 该点的 y 分量。 |
CGSize 关键路径
如果给定属性的值是CGSize
数据类型,则可以将表 C-4中的字段名称之一附加到该属性以获取或设置该值。
结构字段 | 描述 |
---|---|
width | 尺寸的宽度组成部分。 |
height | 尺寸的高度组成部分。 |
CGRect 关键路径
如果给定属性的值是CGRect
数据类型,则可以将表 C-3中的以下字段名称附加到该属性以获取或设置该值。
例如,要更改图层bounds
属性的宽度组件,可以写入bounds.size.width
键路径。
Table C-5 CGRect data structure fields
结构字段 | 描述 |
---|---|
origin | 矩形的原点为CGPoint 。 |
origin.x | 矩形原点的 x 分量。 |
origin.y | 矩形原点的 y 分量。 |
size | 矩形的大小为CGSize 。 |
size.width | 矩形尺寸的宽度组件。 |
size.height | 矩形尺寸的高度组件。 |
2024-06-9(六)