Core Animation 文档翻译 (第二篇)
核心动画基础要素
核心动画为我们APP内Views动画和其他可视化元素动画提供了综合性的实现体系。核心动画不是我们APP内Views的替代品,相反,它是一种结合Views来提供更好性能和支持Content动画的技术。它通过将Views的Content缓存进可以被绘图软件直接操作处理的Bitmaps来达到这种高性能。在某些情况下,这种缓存技术可能需要我们重新思考我们将要如何呈现和管理我们的APP的Content,但是大多数情况我们可以在不去考虑这种缓存带来的影响而直接使用核心动画。除了缓存View 的Content之外,核心动画也定义了一种制定任意可视化Content,使我们的Views与可视化Content结合起来,并使该可视化Content和其他事物一起动画。
我们使用核心动画让我们APP内Views和可视化对象特性的变更以动画的形式过渡。许多可视化对象的变更和修改可视化对象的属性有关联,例如,我们可能使用核心动画来为View的position、size、opacity的改变制作动画。当我们调整了类似的属性,核心动画将会制作在当前属性值和我们指定的新值之间过渡的动画。我们尤其不应该使用核心动画以60次每秒的频率替换View的Content,例如不应该使用核心动画以这种方式制作卡通片。相反,我们使用核心动画从屏幕上移除View的Content,渐隐渐出Content,任意调整Views的图形transformation,或者改变View的其他可视化属性。
Layers 提供绘画和动画的主要部分
Layer 对象是2D平面,并被组织到3D空间;核心动画能做的所有事情的核心也是Layer。和Views一样,Layers管理他们各自的几何、Content和其他可视化属性。和Views不一样的是,Layers没有定义显示表面。一个Layer仅仅管理一个bitmap相关的状态信息;这个bitmap他本身可能是一个View的绘制结果或者我们制定的固定的图片,因为Layers主要管理data,因此APP中我们使用的大部分Layers被视为模型对象。这个概念很重要,需要记住,因为它影响动画的行为。
基于 Layer的绘制模型
APP中许多Layers没有做任何实际绘制。相反,一个Layer捕获我们APP提供的Content并将他们缓存进一个bitmap中,这个bitmap有时候被称作辅助存储。当我们接着改变Layer的属性,所有我们做的便是改变Layer对象相关联的状态信息。当一个改变出发一个动画,核心动画将Layer的bitmap和状态信息传递给绘图硬件,绘图硬件使用这些新的信息渲染bitmap,Figure 1-1图显示的流程。在硬件中操作处理bitmap将会产生更快的动画,相较于使用软件。
因为他操作处理一个静态bitmap,基于Layer的绘制明显不同于传统的基于View的绘制技术。在基于View的绘制中,View的变化通常会调用该View的drawRect:方法 来使用新的参数绘制Content;但是这种方式的绘制的消耗是很高的,因为他是使用CPU在主线程上完成的。核心动画通过一种流程避免这种高昂的消耗--当能够通过操作处理硬件内缓存的bitmap达到相同或相似的效应时候。
尽管核心动画尽可能的使用缓存的Content,我们的APP仍旧提供这个初始化的Content和不时的更新Content。APP中有几个方法为通过Content提供Layer对象,在Providing a Layer’s Contents(后续有翻译)中有详细描述。
基于Layer的动画
Layer对象的数据和状态信息与屏幕上Layer的Content的可视化展现之间是解耦的。这种解耦使得核心动画处于Layer对象与屏幕上可视化的展现之间,并能够使这个从旧状态值到新状态值的过渡显示为动画。例如,改变一个Layer的position属性会引起Layer从它当前position移动到新指定的position。其他属性的相似变更也会引起相应的动画。Figure 1-2展现了几个我们能够在Layers上执行的动画种类。Layer可以触发动画的属性,可以参见Animatable Properties。(后续会有翻译)
在动画的过程期间,核心动画在硬件中做了所有的逐帧绘制。所有我们应该做的仅仅是制定动画的开始和结束点,并开启核心动画,核心动画将会做剩下的工作。我们也能制定自定义的必要的timing信息和动画参数;然而,如果我们没有设定相应的默认值,核心动画将会提供适当的默认值。
更多信息关于如何去开启动画和配置动画参数参见Animating Layer Content。(后续会有翻译)
Layer 对象定义他们的几何形状
Layer的功能之一就为Layer自身的Content管理可视化几何形状。可视化几何形状所包含的信息:Content在屏幕上的bounds和position和该Layer是否通过任何途径被旋转、缩放、transformed。就像一个View,一个Layer也有frame和bounds属性以用于调整该Layer和Content的位置。Layers也有其他和View也有的属性,例如anchor point(锚点),可以围绕锚点制作动画的点。我们指定一些Layer的几何方面值与指定View相应的值是有区别的。
Layers 使用两种坐标系统
Layers 利用基于点的坐标系统和单元坐标系统指定Content的位置。具体使用哪种坐标系统取决于被传递的信息类型。当指定映射到屏幕坐标值和被关联到另外的Layer时候,这两种情况将会使用基于点的坐标,例如Layer的position 属性。当某些值不应该被绑定到屏幕坐标系统时候(因为它是关联一些其他值的)会使用到单元坐标系统。例如,Layer的anchorPoint属性是指定和Layer本身的bounds相关联的点,bounds会变,就会影响单元坐标系统内anchorPoint。
基于点坐标系统最普遍的用途就是指定Layer的size 和position,其实就是指我们使用Layer的bounds和position属性。bounds 定义Layer本身内部坐标系统并包含Layer在屏幕上的size。position属性定义了Layer在它父坐标系统上的位置。尽管Layers有frame属性,frame属性实际上是从bounds和position属性得到的,Layer的frame不常用。
Layer的bounds和frame的方向总是和所在的平台默认方向匹配的。Figure 1-3展示了iOS和OS X两个平台的默认方向。在iOS里面,原点的位置默认是左上角,在OS X平台默认是左下角。如果我们在两个平台之间分享核心动画的代码,我们必须考虑两者的不同之处。
在图1-3中有件事情需要注意:position属性是Layer的中心点。有几个属性的值是基于Layer的anchorPoint属性,position就是其中之一。anchor point 代表了特定坐标系统的起点,锚点被描述的更详细在文档Anchor Points Affect Geometric Manipulations。(后续会有翻译)
有几个属性的值是我们使用单元坐标系统指定的,anchor point 就是其中之一。核心动画使用单元坐标系统代表属性值,这些属性值可能会发生变化当Layer的size改变的时候。我们可以把单元坐标视为一个整体值的百分比(例如宽度被设定为为100,锚点是50%)。在单元坐标内,每个坐标值的变化范围是0.0到1.0。例如沿着X轴,左边缘是0.0,右边缘是1.0。沿着Y轴单元坐标的值的变化和所在平台有关,如图1-4所展示。
注意:直到OS X 10.8,当有需要时,geometryFlipped属性能够调整一个Layer的Y轴方向。当涉及到反转transforms调整时,通过使用这个属性来矫正Layer的方向是很有必要的。例如如果父View使用一个反转transform调整时,子Views的contents(和他们相应的Layers)将会被反转。在这些情况下,设置子Layers的geometryFlipped属性为YES是能够改正问题的一个简单的方式。在OS X 10.8和之后,AppKit 管理这些属性并且我们不应该修改他。对于iOS APPs,完全不建议使用geometryFlipped属性。
所有的坐标值,无论点坐标还是单元坐标,它们都是浮点型数值,使用浮点型使我们能够指定精细的位置,这些位置可能在标准坐标值之间。浮点值的使用很便利,尤其在印刷或者当会知道Retina屏幕时候,Retina屏幕的一个点可能代表多个像素。浮点值允许我们忽略基于设备的分辨率,可直接调整到某个我们想要的精确值。
Anchor Point 影响几何图形操作处理
Layer的几何相关操作都受到Layer的anchor point的影响,即Layer的anchorPoint属性。当调整position或transform属性的时候,anchor point的影响会很明显。position 属性总是和Layer的anchor point关联在一起的;对Layer做的transformations调整也总是和anchor point 关联着的。
图1-5所描述了——Layer的anchor point 从默认值到一个新值的调整是如何影响position属性的。anchor point从Layer的中心点移动到Layer的origin,改变了Layer的position属性,但是Layer在他父Layer的bounds内坐标没有发生变化。
图1-6展现了anchor point的改变是如何影响Layer的transforms变换的。当我们对Layer做旋转transform调整时候,旋转将会绕着anchor point发生。由于anchor point默认在Layer的中心位置,正常的旋转调整将会是围绕Layer的中心发生;如果我们改变anchor point,旋转的结果将会发生变化。
Layer 的三维空间操作
每一个Layer有两个transform 矩阵,我们可以使用这两个矩阵来操作处理Layer的contents。Layer的transform属性指定的变换将会被应用到Layer本身和内嵌的sublayers。通常我们想使用这个属性来调整Layer本身,例如,我们可能使用transform属性来缩放或旋转某个Layer,或者临时调整Layer的position。sublayerTransform属性定义的变换仅仅用来调整Layer的sublayers,在某些场景中它也通常备用来添加人眼视角效应。
Transforms 通过矩阵变换改变当前坐标值得到新的坐标值(coordinate),新的坐标值代表着转换后的坐标值。由于核心动画可以指定三维空间变换,在矩阵变换中,每一个点对应着一个4x4的矩阵。如图1-7.在核心动画中transform代表着CATransform3D类型。幸运的是,我们不需要直接修改这个4x4的矩阵来执行标准的转换;核心动画提供一个全面的函数集合,包含操作有scale,translation,rotation矩阵以及矩阵的比较。除了使用函数操作矩阵之外,核心动画也扩展了KVC,以便于支持我们使用key paths修改transform。可以通过key paths修改的列表参见CATransform3D Key Paths。
关于可用的函数信息和矩阵操作,可以参见Core Animation Function Reference。
图层(layer)树反应动画状态的不同方面
使用核心动画的APP有三种Layer对象集合;在将content显示到屏幕上的任务中,每个Layer对象集合扮演者不同的角色。
·模型树(model layer tree 也被简称为图层树)是和APP交互最多的。模型树上面的模型对象保存着动画的目标值。无论何时我们要改变一个Layer的属性时,我们应该使用model对象。
·呈现树(presentation tree)包含着正在动画过程中的值。无论在哪里模型树总包含动画的目标值(最终值),而渲染树反映屏幕上显示的当前值。我们永远都不要修改呈现树上的对象,而可以借助呈现对象来读取当前动画的值,还可以将该值作为一个新的动画的起点。
·渲染树(render tree)内包含的对象执行真正的动画,并且它是核心动画私有的。
每一个集合内的Layer对象都想APP内的Views一样是以层级结构组织起来的。事实上,一个为所有Views内嵌Layers的APP中,每个(图层)树的结构和View的层级结构是一直的。然而,APP可以添加单独的Layer对象(单独的Layer对象是指的没有被关联到view的层次中的),通过这么做我们可以优化APP的content方面的性能,因为某些content的显示是不需要使用Views的其他功能的。图1-9展示了Layer层次的分解图。在这个例子中window包含一个content View,这个View包含了一个button View和两个单独的Layer对象。每一个View包含一个对应的Layer对象,这个Layer对象用于形成Layer层结构的一部分。
如图1-10所示,每一个图层树上的对象都有一个对应的对象在渲染树和呈现树上。和之前提到的一样,APP开发中主要使用图层树中的对象,但是有时可以需要获取渲染树上的对象。尤其是,通过图层树内Layer对象的presentationLayer属性可以获取对应的呈现树上的对象,我们可以通过获取这个对象读取当前动画过程中某个属性的值。
重要提醒:我们应该仅仅在动画执行过程中获取呈现树上的对象。当一个动画还在执行过程中,渲染树包含Layer在屏幕上那一瞬间显示的值。这个值是不同于模型树的,模型树只会反映动画终点状态的值。
Layers和Views 之间的关系
Layers不是APP中Views的替代品,也就是说我们不能创建一个仅仅基于Layer对象的可视化界面。Layers为View提供关键基础。尤其是,Layers让它绘制Views的Content和给Views的ContentView更高效以及更方便,并且能够维持高帧率。然而,有许多事情是Layer是无法做到的,Layers无法处理事件、绘制content、参与响应者链条以及好多其他事情。出于这些原因,每个APP必须有一个或更多Views去处理这些交互。
在iOS中,每个Views默认被嵌入一个对应的Layer对象,但是在OS X中我们必须自己指定哪些Views应该内建Layer。在OS X v10.8和之后,给所有Views添加Layer才有意义,然而我们我们没必要这么做,当不是必须及指定layer这种消耗没有保证的时候我们仍旧可以禁止为Views的Layers添加。Layers确实增加APP的内存,但是Layers带来的优点多余缺点,因此在禁止Layer的支持前最好先测试下APP的性能。
当我们开启某个View的Layer支持时,就创建了一个被置为layer-backed view。在layer-backed view中,系统是有责任创建该View附属的Layer并同时将创建的Layer与View对应保存。所有的iOS中的Views都是layer-backed ,大部分OS X中的View也是一样的。然而,OS X中,我们也创建Layer-hosting view,这种View需要我们自己创建一个Layer,对于这中View,AppKit具有管理他的方式并且在View改变时候不调整它。
提示:对于layer-backed views而言,无论何时,都推荐操作View而不是操作他的Layer。在iOS中View仅仅是一个Layer对象的简单包装,因此操作处理Layer通常更好。但是,有些时候在iOS和OS X中直接操作Layer可能不会产生预期的效果。此核心动画编程指南将会指出这些陷阱并提供避免他们的方式。
除了和Views关联的Layers外,我们也可以创建一个没有对应View的Layer对象。我们将的Layer对象嵌入到其他Layer对象里,例如嵌入到和View关联的Layer里。我们尤其可以使用单独的Layer作为特殊优化的一部分,例如,如果我们想要使用同样的图片在许多地方,我们可以加载这个图片一次,并使它和许多单独的Layer对象关联,并将这些Layer对象添加到图层树上,每一个Layer于是就引用这个图片资源而不是尝试去再次创建该图片并拷贝到内存中。
关于如何开启Layer的支持,参见Enabling Core Animation Support in Your App。关于如何创建一个Layer对象层次和合适应该创建一个对象层次的建议参见Building a Layer Hierarchy(后续会有译文)。
注:后续我将会有新的翻译更新,觉得不错的朋友可以暂等下