一、关于Core Animation
Core Animation是一个可用以iOS和OSX上的图形渲染与动画的基础结构,可以用于APP的视图或者其他视觉元素的动画。绘制每帧动画的大部分工作都是由Core Animation完成的。我们所要做的就是配置一系列的动画参数(如起点、终点), 并通知 Core Animation启动动画。 然后,Core Animation会完成剩余工作,把大部分实际的绘制工作交由硬件来加速渲染。这种加速,可以在不给CPU加重负担和降低APP运行速度的情况下,带来高帧率和平滑的动画效果。
只要编写iOS程序,就都会用到Core Animation。如下图所示,Core Animation位于APPKit和 UIKit之下,并集成到Cocoa和Cocoa Touch的视图工作流中。当然,Core Animation也有扩展视图的功能,并且允许我们对动画控制的更强。
我们不需要直接使用Core Animation,但是我们也应该理解Core Animation在app的基础结构中扮演什么样的角色。
1、Core Animation管理app的内容
Core Animation不是绘制系统,而是一种基础结构,可以在硬件中组合或操作应用内容。这个基础结构的核心是layer图层对象,用于管理和操作内容。
图层将内容捕获到bitmap图中,以便可以被图形硬件操作。大多数app中,图层可用于管理视图,也可以根据需要创建独立的图层。
2、图层修改触发动画
大多数使用Core Animation创建的动画,都涉及到图层属性的修改。和视图一样,layer对象也有bounds区域,屏幕上的位置,不透明度,转换,和其他许多面向视觉的可以修改的属性。
对大多数属性来说,更改属性的值,会通过图层从旧值到新值得动画来触发隐式动画的创建,如果我们想更好控制生成的动画,我们也可以显式的动画这些属性。
3、图层的层级结构
图层通过层级部署来管理父子关系。像视图内容一样,图层的部署会影响其可见的视图内容。视图对应的一组图层的层级结构,反映了相应的视图的层级结构。我们也可以增加独立的图层到层级体系中,以扩展除视图以外的内容。
4、Action可以让我们改变图层的默认行为
隐式图层动画是使用Action对象来实现的,这些Action对象是实现预定义接口的通用对象。Core Animation使用action对象实现和图层相关的默认动画行为。我们也可以通过创建action对象来实现自定义动画行为。
我们可以分配action对象给图层的属性,当属性改变时,Core Animation会找到action对象,并执行这个action。
二、Core Animation基础
Core Animation提供了一个通用系统,用于动画视图和其他可见元素。Core Animation不是视图的替代品,而是集成视图一起来提供更好的性能并支持动画内容的技术。它通过把视图内容缓存为bitmap来实现,bitmap是图形硬件直接控制的。大多数时候我们只管使用Core Animation,不需要知道有它的存在。除了缓存视图内容,Core Animation也定义了一种将任意可见内容内容与view集成并动画的方法。
我们使用Core Animation来动画视图和其他可视内容的变化。大多数变化和可视对象的属性更改有关。举个例子,我们可以使用Core Animation来动画视图的位置,尺寸,不透明度等变化。当我们做了这个变化时,Core Animation会在属性的当前值和新值之间动画。通常,我们不会每秒60次的使用Core Animation来替换视图的内容,如卡通中。而是我们使用Core Animation来在屏幕中移动视图的内容,淡入淡出这些内容,对内容应用各种图形变换,或者改变视图的其他可见属性。
1、图层提供绘制和动画的基础
图层对象,是在三维空间中组织的2D表面,是核心动画所做的一切的核心。和视图一样,图层管理几何形状,内容,表面各种可见属性的信息。而和视图不一样的是,图层不一定义自己的外观。图层仅管理bitmap周围的状态信息。bitmap是视图绘制自己本身,或者固定图片的结果。因为这个原因,APP中的主图层被认为是一个模型对象,因为它主要管理数据。这个概念很重要,因为它影响着动画行为。
2、基于图层的绘制模型
大多数图层不会进行实际绘制。而是,图层捕获APP提供的内容,并将其缓存在bitmap中,有时候这一过程被称为backing store(存储备份)。接着,当你改变图层的属性时,你正在做的就是更改和图层相关的状态信息。当改变触发了动画时,Core Animation将图层的bitmap和状态信息传递给图形硬件,图形硬件使用新的信息完成bitmap的渲染工作。正如下图所示。
硬件控制bitmap比在软件中做这些工作更快。因为硬件控制的是一个静态的bitmap,所以基于层的绘制和更传统的基于视图的绘制技术相比,有很大不同。对于基于视图的绘制,视图本身的变化常常会引发对视图的drawRect: 方法的调用,来使用新的参数重新绘制内容。但是因为是这种绘制方式是在主线程上使用CPU完成的,因此代价高昂。Core Animation通过操作硬件转换缓存的bitmap来达到了相同或相似的效果,而尽可能的避免了这种开销。
尽管Core Animation尽可能多的使用缓存内容,但是APP仍然必须提供初始内容,并且不时地更新。而APP提供内容的图层对象的几种方式,详见https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/SettingUpLayerObjects/SettingUpLayerObjects.html#//apple_ref/doc/uid/TP40004514-CH13-SW4
3、基于图层的动画
图层对象的数据和状态信息,是和该层在屏幕上的可视表示分离的。这种分离为Core Animation提供了一种插入自身并将从旧值到新值的变化动画的方式。举个例子,改变图层的位置属性,会使Core Animation将图层从当前位置移动到指定的新位置。其他属性的相似改变,也会引起适当的动画。下图表明了我们可以在图层上执行的动画类型。对于可以引发动画的图层属性列表,可以参见动画属性https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/AnimatableProperties/AnimatableProperties.html#//apple_ref/doc/uid/TP40004514-CH11-SW1
在动画的过程中,Core Animation在硬件中为我们完成了所有的逐帧绘制。我们所要做的就是指定动画的起始点,和结束点,然后Core Animation完成剩下的工作。我们也可以按需指定自定义的时间信息和动画参数。如果我们指定的话,Core Animation会我们提供合适的默认值。
4、图层对象定义自己的几何形状
图层的工作之一就是,管理其内容的可视几何形状。可视几何形状包含了关于内容的边界、屏幕上的位置的信息,以及图层是否以某种方式旋转、缩放、转换。和view一样,图层也有frame和bounds,我们可以用来部署图层和其内容的位置。图层也有一些view所没有的其他属性,如定义操作发生位置的锚点。指定图层几何形状的某些方面的方式和指定view的这些信息的方式是不同的。
5、图层使用两种类型的坐标系统
图层使用基于点的坐标系统和单位坐标系统来指定内容的放置。使用哪种坐标系统取决于传输的信息类型。当指定直接映射到屏幕坐标的点,或者指定和另一个图层的相关的点时,使用基于点的坐标系统,如图层的position属性。当值不应该和屏幕坐标绑定时,使用单位坐标系统,因为它是相对于其他值的。比如,图层的anchorPoint属性指定和图层的bounds相关的点,它是会改变的。
通常使用基于点的坐标系统,是使用图层的bounds和position属性来指定图层的尺寸和位置,bounds定义了图层自己的坐标系统,并包含了图层在屏幕上的尺寸。position属性定义了,在其父视图的坐标系统中图层的位置。尽管图层还有个frame属性,但是这个属性通常是由bounds和position属性得来的,因此很少使用frame属性。
图层的bounds和frame的rect方向总是和底层平台的默认方向相匹配的。下图展示了再iOS和OSX系统上的bounds的默认方向。在iOS中,bounds的原点默认是图层的左上角,OSX中则默认是左下角。如果我们想在iOS和OSX两种系统上共享APP版本,那么我们必须考虑到这点不同。
上图中,值得注意的是,position属性定位在图层正中间。这个属性是根据图层的anchorPoint属性值的改变来定义的几个属性之一。锚点表示某一坐标的起点,详细信息请见https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/CoreAnimationBasics/CoreAnimationBasics.html#//apple_ref/doc/uid/TP40004514-CH2-SW17
锚点使用单位坐标系统的几个属性之一。 Core Animation使用单位坐标系统,来表示当图层的尺寸改变时,可能改变的值的属性。我们可以把单位坐标系统看成总值的百分比。在单位坐标空间中,每个坐标都在(0.0,1.0)的范围。举个例子,沿着x轴,左边缘在0.0左标上,右边缘在1.0左标上。沿着y轴,单位坐标的方向取决于平台。如下图所示,
不管是点坐标系统还是单位坐标系统,所有的坐标值都是浮点数。浮点数可以指定位于正常坐标值之间的精确位置。浮点数也比较方便,尤其是在打印或者绘制到retina显示器上时,因为retina显示器上的点事通过多个像素来表示的。浮点值可以允许我们忽略底层设备的分倍率,只需要指定我们需要的准确值即可。
6、锚点影响几何操作
图层的几何相关操作和图层的锚点相关,可以使用anchorPoint属性访问锚点。当操作图层的position和transform属性时,要特别注意锚点的影响。position属性总是和图层的锚点属性相关,我们用到图层上的任何变换,也都和锚点相关。
下图阐述了锚点从默认值到另一个值的改变如何影响了图层的position属性。尽管图层在它的父层bounds内没有移动,但是锚点从图层的中心点移动到了图层的bounds原点,因此改变了position属性的值。
当我们对图层进行旋转变换时,旋转是绕着锚点发生的。因为锚点默认是图层中心点,所以通常这就创建我们所期望的旋转方式。但是,如果我们改变了锚点的位置,那么旋转的结果也就不同了。
7、图层可以在三个维度被操作
每个图层都有两种矩阵变换可以用来控制图层及其内容。CALayer的transform属性,可以指定我们想在图层及其嵌套的子层上使用的变换方式。通常当我们想修改图层时,会使用这个属性。举个列子,我们可以使用这个属性来缩放,或者旋转图层,再或者暂时更改图层的position。 sublayerTransform属性定义了仅对子层使用的变换,通常用来给场景内容增加透视效果。
变换就是给坐标值乘以一个数字矩阵来获取一个新的坐标值,这个新的坐标值就是原来的点坐标变换以后的结果。因为Core Animation值可以在三个维度指定,所以每个坐标都有四个值,必须乘以一个4*4的矩阵,如下图所示。Core Animation中,图中的变换通过 CATransform3D类型来表示。幸运的是,我们不必直接修改这个结构的字段来执行标准变换。Core Animation提供了一套综合的函数来创建缩放、变换、旋转矩阵,以及进行矩阵比较。除了操作矩阵的函数外,Core Animation还扩展了键值编码来使用key path修改变换。可修改的key paths列表见https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/Key-ValueCodingExtensions/Key-ValueCodingExtensions.html#//apple_ref/doc/uid/TP40004514-CH12-SW1
图8展示了我们常见的矩阵变换的配置。给任意坐标乘以单位矩阵的变换返回相同的坐标;对于其他的变换,坐标如何被更改完全取决于你更改了哪些矩阵组件。比如说,如果我们只向沿x轴变换,那就给变换矩阵的tx设为非0值,而ty和tz设为0。而对于旋转,我们只要提供合适的目标旋转角度的正弦和余弦值。
8、图层树反映了动画状态的不同方面
使用Core Animation的APP有三组图层对象。应用在屏幕上出现的内容,每组图层对象都扮演着不同的角色:
- 模型图层树(简称图层树)中的对象是应用程序交互最多的图层。这个树中的对象是存储动画目标值的模型对象。不管什么时候改变图层的属性,我们都会用到这种对象。
- 在表示树中的对象包含了任何正在运行动画的正在运行值。图层树对象包含动画的目标值,而表示树对象反映当前出现在屏幕上的显示值。这个树上对象是不能修改的。而是使用这些对象获取当前动画值,或者从这些值开始创建一个新的动画。
- 呈现树中的对象执行实际的动画,是Core Animation私有的。
和视图一样,每组图层对象被组成一个层级结构。实际上,对于iOS应用程序来说(启用所有视图的图层的应用程序,有些会禁用视图的图层,但是iOS是所有的视图都是层支持的),每组树的初始结构和视图的层级结构是完全匹配的。但是,应用程序可以按照需要添加附加的层对象到图层的层级结构中,也就是说,层不和视图关联。我们可以在不要求视图的内容的情况下,这样做以优化APP的性能。下图展示了一个简单的iOS APP中的图层分解。例子中的window包含一个content view,而这个view本身又包含一个button视图和两个独立的图层对象。每个view都有一个相应的层对象,这些层对象形成了图层的层级体系。
对于图层树中的每个对象,在表示树和呈现树上都有相匹配的对象,就像下图所示。正如前面所提到的,APP主要是和图层树中的对象打交道,但是也会不时的访问表示树中的对象。尤其是,访问图层树中对象的presentationLayer属性,会返回在表示树中相应的对象。你可以通过访问这个对象来获取动画中的一个属性的当前值。
注意:
只有在动画正在运行时,我们可以访问表示树中的对象。当动画正在运行过程中时,表示树会包含那一刻出现在屏幕上的图层值。这点和图层树不同,图层树中一直存储着我们代码中设置的动画最终目标值。
9、图层和视图的关系
首先图层不是视图的替代物。也就是说,我们不能单独的基于图层对象创建一个可视接口。图层为视图提供基础设施。尤其是,图层更加容易和高效地绘制和动画视图内容,并且这样做的同时还保持这高帧率。但是,图层也有很多不能做的事情。图层不能处理事件响应、绘制内容等等。由于这个原因,APP仍然必须有一个或多个view来处理各种交互。
iOS中,每个视图都在相应的层对象支持。层确实会稍微增加应用的内存开销,但是利大于弊,因此最好在禁用图层的支持之前,测一下应用的性能。
除了和view相关联的层外,我们还可以创建没有对应view的层对象。我们可以把这些独立的层对象嵌入到其他层中,包括和view关联的层。通常我们使用单独的层对象作为指定优化路径。比如说,我们想在多个地方使用同一张图片。我们可以只加载图片一次,然后把它和多个层对象关联起来,并添加这些对象到层树中。每个层就会引用到源图片,而不是在内存中创建图片的副本。