[译] Core Animation 编程指南 - Core Animation 的基础

866

本文首发地址

Core Animation 的基础

翻译说明
Layer - 图层
View  - 视图

Core Animation 提供一个通用的系统来对你 App 的视图或者其他可视化元素做动画。Core Animation 并不是替换你 App 的视图,Core Animation 是一种与视图集成的技术,用来给视图内容的动画制作提供更好的性能和支持。Core Animation 通过缓存视图内容转换为图形硬件能直接操作的位图来实现这一行为。在某些情况下,为了缓存视图内容,Core Animation 还定义了一种方法来指定任意可见内容,将该内容与你的视图集成,并将其与其他所有内容一起进行动画。

你使用 Core Animation 来对你 App 的视图和可视对象的改变进行动画。大多数的改变是和你修改的可视化对象的属性相关联的。例如:你可能使用 Core Animation 去使视图的 position size 或 opacity 的改变进行动画。当你进行类似的改变时,Core Animation 会在该属性的当前值和你指定的新值之间做动画。通常,你不会像动画片一样,使用 Core Animation 每秒60次来替换视图的内容。实际上,你会使用 Core Animation 去移动屏幕上视图的内容,淡入淡出内容,对视图应用任意图形形变,或者改变视图的其他可视属性。

图层 提供 Drawing 和 Animations 的基础

图层对象是由 3D 空间管理的 2D surface,它也是 Core Animation的灵魂。和视图相同的是,图层管理 surface 的几何信息,内容,和它可视化属性。和视图不同的是,图层不定义它们自己的外观。图层 对象仅管理位图的状态信息。位图可以是视图绘制自身的结果,也可以是你指定的 image。为此,你在 App 中使用的 main layers 被认为是模型对象,因为它们主要是管理数据。这个概念很重要,因为它影响动画的行为。

基于图层的绘制模型

你的 App 中大多数的图层不会做实际的绘制工作,图层对象会捕获你的 App 中提供的内容,然后将内容缓存为位图,有时也被称为 backing store。当你后面再修改图层的属性时,你所做的只是改变与图层对象相关联的状态信息。当改变触发一个动画时,Core Animation 会将图层的位图和状态信息传递给图形硬件,图形硬件使用新的信息来进行渲染,如图1-1。硬件操作位图进行动画要比软件快得多。

图1-1 Core Animation 如何绘制内容

因为它操作的是静态的位图,所以基于图层的绘制与传统的基于视图的绘制技术有很大的不同。基于视图的绘制,通过调用视图的 drawRect: 方法去使用新的参数去重绘内容来作为视图本身改变的结果。但是使用这种方法是非常耗性能的,因为他在主线程上使用 CPU 。Core Animation 通过硬件直接操作缓存的位图去实现同样的或者类似的效果,来避免性能上的消耗。

虽然 Core Animation 尽可能的使用缓存的内容,但是你的 APP 必须提供初始的内容并且不时的更新它。你的 App 有几种方法来提供图层对象的内容,详情请见提供图层内容

基于图层的动画

图层对象的数据和状态信息是与屏幕上图层的视觉显示分离的。这种分离赋予 Core Animation 一种方式去干预自己,并且对旧状态到新状态的改变进行动画。例如,改变图层的 position 属性 会导致 Core Animation 将图层从当前位置移动到新的指定位置。同样的,改变其他的属性也会导致恰当的改变。如图1-2。图层 属性触发动画机制的列表,可参见可动画的属性

图1-2 你可以在图层上执行的动画的例子

在一个动画执行的过程中,Core Animation 会在硬件中完成所有的逐帧绘制。你所需要做的就是:给动画制定一个开始值和一个结束值,剩下的就可以交给 Core Animation 了。你也可以自定义你所需要的时间信息和动画参数,如果你不自定义,Core Animation 会提供合适的默认值。

关于如何初始化动画和配置动画参数的更多信息,可以参见动画图层内容

图层对象定义它们自己的几何图形

图层的工作之一就是管理其内容的视觉几何(visual geometry)。视觉几何包含其内容的 bounds、在屏幕的 position 和图层是否被旋转、缩放、形变。和视图一样,图层也有 frame、bounds 属性用来定位图层的位置和内容。图层还有视图没有的属性,例如 锚点(anchor point):它定义了操作发生的点。 指定图层几何图形的某些方面的方式也与为视图指定信息的方式不同。

图层使用两种类型的坐标系

图层利用基于点坐标系(point-based coordinate systems)和单位坐标系(unit coordinate systems)来指定内容的位置。使用哪一个坐标系取决于传递信息的类型。当指定的值直接映射到屏幕坐标或必须指定相关联的另一个图层的值时,使用基于点坐标系。例如图层的 position 属性。当值不应与屏幕坐标相关联,因为它与其他值相关联时,使用单位坐标系。例如:图层的 anchorPoint 属性指一个与图层本身的 bounds 相关联的点,该点是可以改变的。

基于点坐标系最常见的用法就是使用图层的 boundsposition 属性指定图层的 sizepositionbounds 定义了它自身的坐标系,并包含了它在屏幕上的 size。position 定义了图层在它父坐标系上的位置。虽然图层有 frame 的属性,但该值实际是由 boundsposition 属性计算出来的,并且图层不会经常使用 frame。

layer 的 boundsframe 属性的方向总是和底层平台的方向一致。下图展示了 iOS 和 OS X 系统的默认方向。在 iOS 中,默认的原点是图层的左上角,而在 OS X 系统上,默认是左下角。如果你想在两个系统上用一套 Core Animation 代码,你必须考虑到这些差异。

图1-3 iOS 和 OS X 默认的图层几何

在图1-3中,需要注意的是, position 属性是位于图层的中间。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 轴上的默认方向。当涉及到 filp 形变的时候,必须使用该属性去帮助图层得到正确的方向。例如:如果父视图使用 flip 形变,它的子视图的内容(以及与它们绑定的 layers)往往会倒置。在这种情况下,将子图层的 geometryFilpped 属性设置为 YES ,是一种简单有效的解决办法。在 OS X 10.8 以后,AppKit 将会自动处理该属性,不需要你再修改。对于 iOS 来说,推荐你不要使用 geometryFlipped 属性。

所有的坐标值,无论是点还是单位坐标值都是浮点数。使用浮点数可以允许你在正常的坐标值之间指定精确的位置。使用浮点数是很便捷的,尤其在输出或者绘制 Retina 显示器时,一个点是由多个像素点绘制的。浮点数使你可以忽略底层设备分辨率而专注于你所需精度的具体值。

锚点影响几何操作

layer 的几何操作的发生于图层的锚点相关联,你可以使用 anchorPoint 属性来访问图层的锚点。当操作图层的 positiontransform 属性时,锚点的影响是非常显著地。position 属性总是和图层的锚点相关联的,你应用到图层上的任何形变效果也是和锚点相关联的。

图1-5 演示了锚点从默认值改变为一个不同的值时,图层的 position 属性是如何变化的。即使图层没有在它的父边界中没有移动,将图层的锚点从中心移动到边界的原点也会改变 position 的值。

图1-5 锚点如何影响 position 属性

图1-6 展示了改变锚点的值是如何影响形变在图层上的应用。当你在图层上应用一个旋转的形变,旋转发生在锚点的周围。因为锚点默认是在图层的中间,这通常会创建一个你期待的旋转行为,如果你改变锚点,旋转的结果是不同的。

图1-6 锚点是如何影响形变

Layers 可以在三维空间中操作

每个图层有两个形变矩阵,你可以用这两个矩阵去操作图层和它的内容。CALayer 的 transform 指的是你想应用在它和它的 sublayers 上形变。在你想改变图层本身的时候使用该属性。例如,你可能想使用该属性去缩放、旋转或者暂时改变它的 position。sublayerTransform 属性只应用于 sublayers,它通常用于向场景内容添加透视效果。

通过将坐标值乘以数字矩阵来进行形变工作,以获得表示原始点形变版本的新坐标。因为 Core Animation 的值可以在三维空间中指定,每个坐标点有四个值,坐标点必须和一个 4x4 矩阵相乘,如图1-7所示。在 Core Animation,图中的形变是通过 CATransformD 类型来表示的。幸运的是,你不必去修改矩阵结构的字段来执行标准的形变。Core Animation 提供了丰富的函数去创建缩放、平移和旋转矩阵去做矩阵比较。为了使用函数来操作形变,Core Animation 延伸了键值编码支持,使你可以使用键来修改形变。查看 CATransform3D 键路径 获悉你可修改的键。

图1-7 使用矩阵运算来转换坐标

图1-8 显示了一些常见形变的矩阵配置。任何坐标乘以 identity 矩阵返回坐标本身。对于其他形变,坐标的修改完全取决于你改变的矩阵元素。例如,沿 x轴平移,你可以将形变矩阵的 tx 设为非零值,将 ty 和 tz 设为0。对于旋转,你可以提供目标旋转角度响应的正弦值和余弦值。

图1-8 常见的形变矩阵配置

图层树反映了动画状态的不同方面

App 使用 Core Animation 有三组图层对象。每组的图层对象对将你的 App 的内容显示在屏幕上都有不同的作用:

  • 模型图层树(图层树 - layer tree)中的对象是与你的 App 交互最多的。该树上的模型对象存储任何动画的目标值。每当你改变图层的属性时,都使用了其中一个对象。
  • 表示树(presentation tree)中的对象包含了任何正在执行的动画的当前值。 虽然图层树对象包含动画的目标值,但表示树中的对象会反映屏幕上显示的当前值。你永远不要修改该树中的对象。你可以使用该树的对象来获取当前动画的值,也许你需要使用这些值来创建一个新的动画。
  • 渲染树(render tree)中的对象用来执行实际的动画,并且它是 Core Animation 的私有对象。

每组图层对象都被组织成一个分层结构,就像 App 中的视图。实际上, 对于为其所有视图启用图层的 App,每个树的初始结构完全匹配视图层次结构。但是,App 可以根据需要将其他图层对象(即与视图无关的图层)添加到图层层次结构中。你可以在这种情况下执行此操作,以针对不需要视图的所有开销的内容优化应用程序的性能。图1-9显示了在简单的iOS应用程序中找到的图层细分。示例中的 window 包含一个内容视图,该视图本身包含一个按钮视图和两个独立的图层对象。每个视图都有一个对应的图层对象,它构成图层层次结构的一部分。

图1-9 与 window 关联的图层

图层树中的每个对象,都在表示树和渲染树中有一个与之匹配的对象,如图1-10 所示。如上所述, App 主要使用图层树中的对象,但有时也会访问表示树中的对象。具体而言,访问图层树中的对象的 presentationLayer 属性来获取表示树中与之关联的对象。你可能需要访问对象来获取动画中间过程的属性的值。

图1-10 window 的图层树

你应该只在动画执行中的时候去访问表示树中的对象。当动画正在进行时,表示树包含当时在屏幕上显示的图层值。这个行为不同于图层树,图层树总是反映你的代码设置的最后的值,并且等同于动画的最终状态。

图层和视图的关系

图层不能替代你 App 中的视图,即你不能仅基于图层来创建视觉页面。图层提供视图的基础结构。图层使绘制和动画视图的内容更加容易高效,并且保持高帧速率。然而,图层也有很多事情处理不了。图层不能处理事件、绘制内容、参与响应链,和其它很多事情。基于此,每个 App 必须有一个或者多个视图来处理上述类型的交互。

在 iOS 中,每个视图底层都由图层对象支持,但是在 OS X 你必须决定哪个视图有图层。在 OS X v10.8 以后的版本,给所有的视图添加图层可能是有意义的。然而,你不被要求必须这么做,你可以在不需要或者不想的地方使图层失效。图层会使你的内存开销增加,但是它们的优点远远超过这个缺点。在禁用图层支持之前,最好先测试 App 的性能。

当你的视图支持图层的时候,你创建了一个图层支持的视图(layer-backed view)。对于图层支持的视图,系统会在底层创建一个图层对象,并且保持图层和视图同步。 iOS 的所有视图和大部分的 OS X 视图是图层支持的视图。然而,在 OS X 中,你也可以创建图层托管视图(layer-hosting view),即你自己创建图层对象的视图。对于图层托管视图,AppKit采用了一种不干涉的方法来管理该层,并且不会根据视图的变化来修改它。

对于图层支持视图,无论何时,推荐你操作视图,而不是图层。在 iOS ,视图只是图层的一种封装,所以,你在图层上做的任何操作通常会正常工作。但是在 iOS 和 OS X 中都存在这样的情况:操纵图层而不是视图可能无法产生预期的结果。只要有可能,本文档指出了这些陷阱,并试图提供方法来帮助你解决这些问题。

除了与视图关联的图层之外,还可以创建没有相应视图的图层对象。你可以将这些独立的图层对象嵌入到 App 中的任何其他图层对象中,包括那些与视图相关联的图层对象。你通常使用独立的图层对象作为特定优化路径的一部分。例如,如果你想在多个地方使用相同的图像,你可以加载图像一次,并将其与多个独立的图层对象相关联,然后将这些对象添加到图层树中。然后,每个图层引用源图像,而不是试图在内存中创建自己的图像副本。

有关如何为应用程序视图启用图层支持的信息,请参见在你的 App 中支持 Core Animation。有关如何创建图层对象层次结构的信息,以及何时创建图层对象层次结构的提示,请参见构建图层层次结构

最后