周末闲来无事, 复习一下Core Animation的相关知识, 网页正好跳到Core Animation Basics页面, 那么就从最基本的概念先入手看看.
Core Animation提供了一个通用的系统来展示动画. 它并不是app中view的替代物. 相反, 它是一种可以和view协同工作的技术以此来提供更好的视觉表现以及对动画做出更好的支持. 而完成以上工作则是通过将view中的内容缓存进位图中, 然后供图形设备直接操作. 在一些情况下, 你可能需要思考你要如何展示和管理app中的内容, 但是大多数情况下, 你可以直接使用Core Animation而不需要问其所以然. 除了缓存view的内容, Core Animation也可以定义一种方法来指定任意的视觉内容, 将内容和view整合起来, 最后实现动画过程.
你使用Core Animation来模拟app中view和视觉对象的变化. 绝大多数的改变都涉及到修改视觉对象的属性. 比如, 你可能使用Core Animation来模拟一个view位置, 大小或是透明度的改变. 当你做这样一个改变的时候, Core Animation就在模拟当前值到新值的变化过程. 你通常不要用Core Animation以1秒60次的速度来替换view中的内容, 相反, 你应该使用Core Animation来移动view中的内容, 实现淡入淡出的效果, 或是实现图形的转变以及其他视觉属性的改变.
Layers为绘图和动画提供了基础
Layer 是Core Animation中的核心, 和view类似, layers也管理着view表层上几何学, 内容和视觉属性的信息. 但不同于view的是, layers并不定义appearance, 而是仅仅管理着位图的状态信息. 这个位图可以是view本身, 也可以是你指定的一张固定的图片. 基于这个原因, 在你app中的主要的layer就被认为是模型对象, 因为它们管理着数据. 这个概念很重要, 因为它会影响动画的行为.
基于Layer的绘图模型
App中的绝大多数layer并不做实际的绘图工作. 相反, 一个layer会捕获app提供的内容, 并且将它缓存到一个位图里, 就相当于是备份下来. 当你之后改变了layer的一个属性时, 你所做的一切都只是改变跟layer对象相关联的状态信息. 当一个改变触发了动画, Core Animation会把layer的位图和状态信息传递给图形硬件, 而图形硬件做的事情就是用新的信息来渲染位图. 如下图所示:
因为这个过程操作的是一张静态的位图, 所以基于layer的绘图是明显有别于传统上基于view的绘图过程的. 在基于view的绘图过程中, 对于view的改变总是会调用view的drawRect:
方法, 以此来用新的参数重绘内容. 这个过程的耗费是很大的, 因为整个操作是在主线程, 并且会占用CPU的资源. 而对于Core Animation来讲, 其整个绘图过程都是尽可能在硬件中操作缓存下来的位图以此来达到绘图的效果, 两者在绘图过程中达到的效果是一样的, 但是基于layer的绘图过程明显消耗更低.
基于Layer的动画
一个layer对象展示的屏幕上时, 其数据和状态信息是分离的, 这个分离就导致Core Animation可以用动画来展示旧状态到新状态的变化过程. 比如, 改变一个layer的位置属性就会导致Core Animation把layer从现在的位置移动到新的位置. 下图展示了几个在layer上可以实现的动画类型.
在动画过程中, Core Animation是在硬件中一帧一帧的绘制. 你需要做的就是指定开始点和结束点, 剩下的工作就全部交给Core Animation去做.
Layer对象定义了自身的几何属性
layer的一个作用就是管理其内容的视觉几何信息. 这些信息包括bounds
, 屏幕上的位置, 是否被旋转, 缩放或形变. 像view一样, layer也有frame
和bounds
, 你可以用它们来指定layer的位置和其内容. layer还有view所没有的属性, 比如anchor point
, 它定义了操作发生处的位置. 指定一个layer的几何信息有时也和指定view的信息时有所不同.
Layers使用两种不同的坐标系统
Layers使用两种坐标系统, 基于点的坐标系统和单位坐标系统. 使用哪种坐标系统取决于传递什么样的值进来. 当传递具体的数值时, 使用的就是基于点的坐标系统. 当传递的值并不是固定的数值时(相对值), 使用的就是单位坐标系统.
在点坐标系统中, 设置一个layer的大小和位置最常用的属性就是bounds
和 position
. bounds
包含了layer自身的坐标系统和其在屏幕上的大小. position
定义了layer自身相对于其父坐标系统的位置. 尽管layer也有frame
属性, 但是它却很少被使用, 因为它的值就是从bounds
和position
属性中传来的.
layer的方向是基于它处在哪个平台上. 下图展示了iOS和OS X系统上默认的方向. 在iOS系统中, 坐标系统的(0,0)点是在layer的左上角, OS X系统上则是在左下角. 如果将Core Animation的代码在这两个平台之间都使用, 那么要注意坐标系原点是有所不同的.
注:
position
属性是在layer的中心处.
锚点是用单位坐标系统来指定的. Core Animation用单位坐标来代表一些当layer的尺寸改变时其值可能会发生改变的属性. 你可以把单位坐标看作是相对坐标. 每个坐标在单位坐标系中都有一个范围, 即0.0 - 1.0. 比如在x轴方向, 左边缘就是在坐标系的0.0处, 右边缘就是在坐标系的1.0处. 而在y轴方向, 值的改变有所不同, 如下图所示:
所有坐标的值, 不论是点坐标还是单位坐标, 都是以浮点数的类型设定的. 这样的设置可以让你指定更精确的位置属性.
锚点影响几何操作
和一个layer几何属性相关的操作都是相对于layer的锚点来发生的, 锚点是可以通过调用layer的anchorPoint
属性获得. 当操作一个layer的位置或者形变时, 锚点的影响是最为显著的. position
属性总是相对于layer的锚点来设定的. 而任何layer上发生的形变也是相对于锚点的.
下图展示了将锚点的值从默认值改变到一个其他值时, 其是如何影响layer的position
属性值的变化的. 尽管相对于父坐标系来说, layer并没有发生移动, 但是将锚点从layer的中心移动到layer的原点会改变position
的值.
下图展示了改变锚点是如何影响形变的. 当对一个layer实施旋转操作时, 旋转是围绕这锚点来旋转的. 因为锚点默认是在layer的中心点, 所以旋转就如何我们期望的样子一样. 但如果你改变了锚点的位置, 那么旋转就和我们所想象的旋转有所不同了.
Layers可以在三个维度上进行操作
每个layer有两个形变矩阵, 你可以用来操作layer以及它的内容. CALayer的transform
属性指定了你想实施形变的layer以及它的子layer. 所以当你想改变layer的时候你就可以使用这个属性. 比如, 你可以使用这个属性来对layer进行缩放,旋转或者改变其位置. sublayerTransform
属性定义了额外的形变, 其只能应用于子layer上, 该属性最常用与给一个场景添加一个透视效果.
形变的发生是通过坐标值和矩阵相乘得到新坐标而实现的. 因为Core Animation的值可以从三个维度上指定, 而每个坐标点有4个值, 所以要乘以一个4x4的矩阵, 如下图所示. 在Core Animation中, 下图的形变是通过CATransform3D的类型来展现的. 幸运的是, 你不需要直接来改变以下结构来实现形变. Core Animation提供了一系列易于理解的函数来实现缩放, 形变, 旋转和矩阵比较. 除了通过函数操作形变以外, Core Animation还支持KVC. 具体键值列表请看CATransform3D Key Paths
下图展示了一些常见的形变中矩阵的配置信息.
Layer树反映动画状态的不同方面
一个使用Core Animation的app有三组layer对象. 每一组layer对象在展示内容的过程中都扮演着不同的角色.
-
处在
layer树
上的对象是和app交互最多的对象. 处在这个树上的对象存储着任何动画的目标值. 一旦你改变了一个layer的属性, 你就会使用到这些对象. -
处在
展现树
上的对象包含了正在进行的动画的动态数据. 尽管layer树
上的对象包含了一个动画的目标值, 但是展现树
上的对象反映的是此时此刻显示在屏幕上的实时的值. 你不应该改变处在展现树
上的对象的值. 相反, 你应该使用这些对象来读取当前的值, 也许可以以这些值为起点来创建新的动画. -
处在
渲染树
上的对象就是在展示实际的动画, 并且对Core Animation来说是私有的.
每一组layer对象都被有序的组织在一个层级结构中, 就想app中的view一样. 事实上, 对于一个允许其所有view都带layer的app来说, 每个树的初始结构都和view的层级是一致的. 然而, 一个app可以往layer层级中添加额外的layer对象, 这就是和view无关联的layer. 下图展示了一个简单的iOS app中layer的分解. 例子中的window里包含一个content view, 其中包含一个button view和两个独立的layer对象. 每一个view都有一个相对应layer对象, 以此来形成layer层级.
对于每一个在layer树
中的对象来说, 在展现树
和渲染树
上都有与之对应的对象. 如下图所示. 前面也有提到, app主要是和layer树
上的对象打交道, 但偶尔也会和展现树
上的对象交互. 需要指出的是, 在layer树
上获取一个对象在presentationLayer
的属性会返回在展现树
上相对应的对象. 你可能在动画进行到一半的时候想获得现在一个属性的值.
注意: 只有动画正在进行的时候, 你才应该获取
展现树
上的对象. 动画在进行的过程中,展现树
上的对象包含了此时此刻出现在屏幕上的layer的值. 这不同于layer树
, 它总是返回代码设置好的结束值.
Layers 和 Views 的关系
Layers并不是app的view的替代. 这也就是说, 你不能只是通过一个layer对象就创建一个视觉界面. layer为view提供了基础. 具体来讲, layer使得绘图和动画都更有效率. 然而, 也有很多事情是layer所不能做的. layer不能处理事件, 不能绘制内容, 也不能参与到响应者链中等等. 基于这些原因, 每一个App都至少要有一个或者更多个view来处理交互.
在iOS中, 每一个view背后都有一个相对应的layer对象, 而在OS X中你需要自己觉得哪个view需要有layer. 在OS X v10.8 以及之后的版本中, 给每一个view添加一个layer对象看起来是非常有意义的. 然而, 也并不是必须要这么做. Layer会增加app的体积, 当然它的缺点可以和优点相互抵消. 所以比较好的做法就是禁用layer前先对app的表现进行测试.
当你允许view支持layer时, 你就创建了一个基于layer的view. 在基于layer的view中, 系统会创建下层的layer对象并且保持其和view的同步. 所有的iOS view都是基于layer的, 在OS X中的大多数view也是如此. 然而, 在OS X中, 你可以创建一个layer-hosting view
, 其特点就是该view的layer是你自己提供的. 对于这样的view, 在view改变时, AppKit并不会改变layer.
注意: 对基于layer的view而言, 无论什么时候都推荐你去操作view, 而不是它的layer. 在iOS中, view仅仅是layer对象外一层薄薄的包装, 所以任何你对layer的操作都是能够达到效果. 但是在iOS和OS X中, 有时候直接对layer进行操作可能并不会产生理想的效果.
除了和view相关联的layer外, 你也可以创建一个单独的layer对象, 即这个对象没有相对应的view. 你可以把这些单独的layer对象镶嵌进app中的其他layer对象中, 包括那些带有相对应view的layer对象. 你通常可以使用单独的layer对象作为一个特定的优化路径的组成部分.
关于如何让view支持layer, 可以参照Enabling Core Animation Support in Your App. 而如何创建layer对象层级, 以及一些注意点, 可以参照Building a Layer Hierarchy.
网友评论