Core Animation编程指导(二)-Core Anima
CoreAnimation提供一个用来动画展示视图和其它视觉元素的通用系统. CoreAnimation并不能代替APP中的view, 相反, 它是一种与view结合使用的技术, 用来提供更好的性能和动画支持. 它通过将view的内容缓存在bitmap(bitmap可以直接被硬件操控)中来实现这中特性的. 在某些情况下, 这种缓存行为可能需要你重新考虑如何呈现和管理应用程序的内容, 但是大多数时候你使用CoreAnimation时, 你并不知道它的存在. 除了缓存视图内容之外, CoreAnimation还定义了一种方法来指定任意视觉内容, 将该内容和view结合, 并将其与其他内容一起动画显示.
使用CoreAnimation动画展示view或其他可是对象的改变. 大多数修改是涉及到视觉对象的属性. 例如, 你可以使用CoreAnimation来对view的position, size,或透明度的改变进行动画. 当你修改这些属性CoreAnimation的动画帧率低于每秒60帧(动画一般是每秒60帧). 相反, 你可以使用CoreAnimation在屏幕上移动view的内容, 淡入淡出该内容, 对view使用变换, 或者改变其他视觉属性.
Core Animation 基础知识
Layer是图形绘制和动画的基础
layer对象是3D空间的二维表示, 它是CoreAnimation能力的核心对象. 类似view, layer管理你的界面的几何, 内容, 以及一些其他视觉相关的特征信息. 和view的区别是, layer没有定义自己的外观, 而是仅仅管理着和bitmap有关的一些状态信息. 这个bitmap可以来源view自己绘制的或者由你设置的Image. 因此, App中你使用的main layer可以视为App的模型对象, 因为他们管理着数据. 记住这个概念很重要, 因为这会影响动画的行为.
基于Layer的绘制模型
绝大多数layer不会进行实际绘制, 相反, 一个layer对象会捕获App中的图形内容, 然后将这些内容保存为bitmap, 有时layer对象也被称为view的后背存储. 之后当你修改layer的某个属性时, 你所做的只是改变和layer相关联的状态信息. 当某次改变产生了动画, CoreAnimation会将layer的bitmap和新的状态信息递交给图形硬件, 这个图形硬件工作是将新状态的bitmap进行渲染, 如图1-1所示. 在硬件中渲染比在通过软件做动画更快.
图1-1CoreAnimation是如何绘制内容的
因为layer是通过操纵静态的bitmap, 这和传统基于view的绘制是有很大的区别的. view的更新一般是通过调用view的drawRect:
方法并穿一个参数过去来绘制内容的. 但这是比较高消耗的, 因为这个操作要占用CPU并在主线程中完成的. 而基于layer的绘制是由CoreAnimation将新的bitmap提交图形硬件完成同样的内容更新, 这样可以节省CPU时间, 也不会堵塞主线程.
尽管CoreAnimation会尽量使用缓存内容, 但是App必须提供初始的layer内容, 并且要时不时地更新. 创建带内容的layer方法有多种, 在本系列文章中的第三篇中的为layer提供内容部分会具体讲到
基于Layer的动画
layer的数据和状态信息会和layer在屏幕上的视觉呈现分离开来(解耦). 这个解耦过程让CoreAnimation可以在layer的状态新值和旧值间插入自己和动画. 举个例子, 改变layer的position属性会导致CoreAnimation将layer从当前位置移动到新位置. 类似地, 改变其他属性也产生一个合适的动画. 图1-2展示了几个常见的layer属性动画. 要看全部的layer属性动画, 请看后面系列章节-补充中的Animatable属性
在动画的过程中, 动画的是有CoreAnimation在硬件上一帧一帧的画出来的. 你只需要设置动画的起始和结尾点就可以了. 你可以设置自定的timing信息或者其他动画属性, 如果你不设置这些值, CoreAnimation会自动帮你设置默认值.
关于如何创建animation对象和设置animation参数, 请看本系列文章四- Core Animation编程指导(四)-对Layer的content做动画
layer对象定义了自己的几何表示
layer的工作之一就是管理内容几何表示. 可见的几何图形包含了内容的位置, 边界(bounds), 以及layer是否旋转, 是否缩放, 是否变换过这些信息. 和view一样, 一个layer对象使用frame和bounds这两个属性来定位layer和layer内中的内容. layer拥有view没有的属性, 比如anchor point, 该属性定义了操作发生的点. layer的某些几何学上的设置也和view中的设置不同.
layer使用两种类型的坐标系
layer使用基于点的坐标系和单元坐标系(unit coordinate system)来确定内容的位置. 使用哪个坐标系取决于layer所传达的消息类型. 基于点坐标系通常用来设置的值和其他layer相关或直接映射到屏幕上的坐标系, 比如layer的position
属性. 单元坐标系用来设置的值和屏幕位置无关而是和其他值有关, 比如layer的anchorPoint
属性, 该属性的值和屏幕无关而是和layer自身的bounds有关, 因为bounds会变化.
通常在定位layer的位置和大小时, 使用点坐标系. 像layer的属性bounds
和position
. bounds
属性定义了layer自己的坐标系和layer在屏幕上的大小. position
确定了layer在父坐标系中的位置. 尽管layer也有属性frame
属性, 但是不怎么常用, 因为frame是一个计算属性, 是通过position
和bounds
计算而来.
layer的bounds
和frame
属性都是基于iOS的默认坐标系, iOS中的默认坐标系的远点在左上方, 是ULO型, 而OSX中的坐标系是LLO型, 所以在两个平台共用一份CoreAnimation代码时要注意这之间的区别.
注意上图中的
position
属性是指layer的中心点, 这个属性是layer属性中受anchorPoint
影响的几个属性之一. 锚点(anchorPoint)表示某个点在坐标系中的起始位置.
锚点是少数几个使用单元坐标系的属性. CoreAnimation使用单元坐标系来表示因layer的大小改变而改变的属性. 单元坐标系的单位是百分比, 范围是0.0-1.0. 比如, 沿x轴, 左边起点是0.0, 右边结束是1.0, 沿y轴的话, 要看你使用的平台, 如图1-4.
图1-4 iOS和OSX中的默认单元坐标系
注意:在OS X 10.8之前
geometryFlipped
属性是用来修改y轴的方向的. 当涉及flip transform时, 某些时候你可以使用该属性来纠正layer的方向. 比如, 如果父view使用了flip transform, 其子view(以及对应的layer)中的内容也会被反转. 在这种情况下, 将子layer的geometryFlipped
属性设置为YES可以解决这个问题. 在OS X 10.8及以后版本, APPKit会为你管理这个属性, 所以你不需要关系. 对于iOS中的APP来说, 你可以完全忽略它.
不管使用哪个坐标系, 坐标值都是浮点类型的. 使用浮点值的目的是为了定位精确, 浮点数的使用允许你指定可能位于整数坐标值之间的精确值. 使用浮点数是比较方便的, 特别是在retina显示屏(一个点中包含多个像素点)上进行绘制时, 使用浮点数让你不用考虑设备屏幕的分辨率, 你只需要关注你想要的精度即可.
锚点对几何操作的影响
对layer的几何操作都是基于锚点的, 特别是当你在对layer做position
和transform
的更新时, 锚点对这影响很大. 图1-5展示了改变锚点对position
的影响. 即使layer没有移动layer, 但是你通过将锚点从layer的中心移到bounds的origin位置, layer的position
属性会自动改变.
图1-6展示了改变layer的锚点是如何影响layer的transform
的. 当你对layer加上一个旋转变换时, 该旋转是以锚点为中心进行的. layer的锚点默认设置在layer的中心点. 所以正常的加上旋转不会有啥大问题的, 但是你将锚点改变后, 旋转的结果可能就是不是想要的.
layer能在进行三维操作
每个layer都有两个transform矩阵, 你可以使用这两个矩阵来控制layer和layer中的内容. 类CALayer
的属性transform
可以控制layer和layer中的内容. 通常使用transform
属性来控制layer本身, 比如缩放或旋转或临时更改其位置. 而属性sublayerTransform
是用来加在layer中的子layer, 并且通常用于向场景的内容添加透视视觉效果.
transform(变换)是通过将坐标点乘以一个矩阵来得到一个新的坐标点的实现的. 因为CoreAnimation中的坐标是三维的, 所以每个坐标点有四个值. 变换时需要使用一个4x4的矩阵, 如图1-7. 在CoreAnimation中使用类型为CATransform3D
的结构体来表示图中的transform. 幸运的是, CoreAnimation已经帮你定义了好一大堆用于操纵CATransform3D
结构体的函数, 比如创建一个scale(缩放), translation(位移), rotation(旋转)类型的矩阵, 还有可以用于比较两个transform矩阵的函数. 另外, CoreAnimation扩展了KVC来支持让你通过keyPath来修改transform, 对于你可以修改的keyPath列表, 你可以查看补充中的CATransform3D Key Paths
图1-8显示了一些更常见的转换的矩阵设置. 通过任意坐标乘identical transform矩阵会返回完全相同的坐标。对于其他转换,如何修改坐标完全取决于您更改哪些矩阵中的组件。例如,为了只沿x轴进行转换,需要为转换矩阵的tx组件提供一个非零值,并将ty和tz值保留为0。对于旋转,你需要提供目标旋转角适当的正弦和余弦值的。
图1-8 常用transform的数学矩阵构造
layer的层级树能够反映不同方面的动画状态
app在使用CoreAnimation的时候, 会拥有三种类型的layer. 每种layer在APP显示内容的过程中起到不同的作用:
- model layer tree(简称layer tree)中的layer, 这个对象用来存储动画target值的model对象. 当你修改layer的属性时, 改变的就是个这个layer
- presentation tree中的layer, 该树包含的是运行中动画的动态数据. 鉴于layer tree中包含的是动画的target信息, 所以presentation tree中的layer反映了当前屏幕上显示的值. 所以不要去修改presentation中的layer对象, 但是你可以获取该树中的layer对象保存的当前动画信息来创建一个新的动画.
- render tree中的layer是CoreAnimation私有的, 实际用来展示动画的.
上面三组layer和组织结构和view类似. 事实上, 对于一个为所有view开启layer的APP来说, 每个layer树的初始结构和相关联的view结构一致. 但APP可以往初始化树中添加额外的layer(和view无关连的), 这样做, 你或许可以提供性能, 因为如果只是单独显示内容的话, 无需使用view, 使用layer就行了. 图1-9显示的是一个demo中的layer分解图. 在该demo中, window包含一个content视图, 视图中又有一个button和两个独立的layer对象. 每个view都有一个与之相对应的layer对象, 该layer对象是layer树的组成部分.
图1-9 layer
layer tree中的每个对象在presentation tree和render tree中存在与之相对应的对象, 如图1-10所示. 正如前面提到过, App的主要工作集中在layer tree, 偶尔会访问presentation tree中的对象. 特别地, 通过访问layer tree中对象的presentationLayer
属性能够得到presentation tree中相对应的对象. 这样你可以通过presentation tree中对象来获取动画过程中layer的属性信息.
重要: 仅当动画运行时, 你才能访问presentation tree中的layer对象. 动画过程中, presentation tree中layer值为当前那一刻的. 然而layer tree中的layer反应的最终状态, 等于你代码中animation的final state.
layer和view之间的关系
layer不能完全替代view, 也就是说, 你的App界面不能完全有layer来搭建. layer是view的基础, 特别当需要为view添加高频动画或者绘制一些内容时. 但layer也有自身的局限性, 比如, layer不能处理用户事件, 所以无法和用户交互. 所以APP中至少需要一个或更多的view来处理交互.
在iOS中, 每个view的背后都有个layer对象, 但在OS X中你必须自己决定那个view需要layer. 在OS X v10.8和之后的版本中, 往所有的view中添加layer是可取的, 但你也可以不这样做, 也可以将某个view后的layer disable掉. layer确实增加了应用程序的内存开销,但它们的好处往往大于缺点,所以在禁用layer支持之前最好测试应用程序的性能。
layer-backed view是指一个view背后有一个layer支持, 在layer-backed视图中, 系统会创建底层的layer并保持和view同步. 所有的iOS视图都是layer-backed, 而且大多数OS X也是这样的. 但是, 在OS X中, 你还可以创建layer-hosting view-由你自己为view提供layer对象. 对于layer-hosting视图, APPKit采用的是一种不负责的管理方法来管理layer, 也不会负责layer和视图的同步, 也就是说, view改变了, 不回去相应的修改layer.
注意:对于layer-backed视图, 建议你尽可能地操作view, 而不是layer. 在iOS中, 视图只是一个对layer对象的简单包装, 所以你对layer所做的任何操作不会有啥问题. 但是, 在iOS和OSX中都存在这样的情况, 直接操作layer而不是view会产生不可预期的后果, 后续章节, 会尽力指出这些陷阱.
除了与视图相关的layer之外,还可以创建没有对应视图的layer对象。您可以将这些独立的layer对象嵌入应用程序中任何其他layer对象的内部,包括与视图关联的那些layer对象。您通常使用独立的layer对象作为特定优化路径的一部分。例如,如果您想在多个地方使用相同的图像,则可以加载该图像一次,并将其与多个独立layer对象相关联,并将这些对象添加到layer树中。然后,每个layer都引用源图像,而不是试图在内存中创建该图像的自己的副本。