怪物有好多层,洋葱有好多层,你知道吗?我们都有好多。--Shrek
Core Animation
是一个有误导性的名字。你也许猜测它的主要目的是用于动画制作,然而事实上动画只是这个框架中的一方面,这个名字最初用于Layer Kit的动画核心的简称。
Core Animation
是一个组合引擎,他的主要职责在于在屏幕上尽可能快地将不同的视觉内容组合起来。这些内容被分成独立的层存储在叫图层树的层次结构中。图层树组成了整个UIKit的根基,以及你在一个iOS应用中所有在屏幕上的东西。
在我们开始讨论动画之前,我们将从图层树开始讲解Core Animation
的静态构成和布局特征。
图层和视图
如果你先前开发过一个iOS或Mac OS的应用,你可能会对视图的概念比较熟悉。一个视图是一个用于显示内容(如图象、文字或视频等)的矩形对象,并且处理用户鼠标点击或者触摸手势等的输入行为。视图可以构建于另一个视图中来形成一种层次的结构,在这种层次中每个视图控制它子视图的位置。图1.1展示了一个典型的视图层次。
在iOS中,视图都继承自一个共同的基类——UIView
。UIView
用于处理触摸事件、支持基于Core Graphics
的绘制、仿射变形(如旋转或缩放)以及如滑动、渐变等简单动画。
CALayer
CALayer
的概念与UIView
非常类似。像视图一样,图层也是可以组成层次树结构的矩形对象,并同样包含如图象、文字或背景颜色等内容,控制着子图层的位置。它们有用于实现动画和变形的方法和属性,而UIView
的主要特性中用户交互是唯一不归CALayer
处理的。
CALayer
并无法察觉响应者链(iOS用于在视图层次间传递触摸事件的机制),因此尽管它确实提供方法用于确认是否一个确定的触摸点在某个图层之中(这将在第三章“图层几何学”中进一步讲述),CALayer
并不能响应事件。
平行层次
每一个UIView
都有一个layer属性,这个属性是一个CALayer
的实例。视图负责创建和管理这个layer,并确保当子视图从视图层次结构中增加或移除时,它们背后的图层会在图层树中平行地联系起来(如图1.2所示)。
就是这些背后的图层负责你在屏幕上看见的一切东西的显示和动画。UIView
是一个简单的包装用于提供针对iOS的方法,诸如触摸处理以及相应Core Animation
的底层方法的高层接口。
为什么iOS有这样两个基于UIView
和CALayer
的平行层次结构?为什么不是一个结构用于处理一切?原因是用于分离职责来防止重复性的代码。事件和用户接口在iOS和Mac OS上的工作相当不同。一个基于多点触控的用户接口从根本上有别于基于鼠标和键盘的用户接口。这就是为什么iOS有UIKit
和UIView
而Mac OS有AppKit
和NSView
。他们功能相似却在实现上有显著差异。
相比之下绘制、布局以及动画在如iPhone和iPad的触摸屏设备下和笔记本和台式机之中的概念几乎一致。通过分离出这些功能逻辑到独立的Core Animation
框架中,Apple可以在iOS和Mac OS中共用这些代码,使得对于Apple自己的操作系统开发小组和第三方开发者们在开发针对这两种平台的应用时有更为相似的体验。
事实上,它们并不是两个,而是四个这样的层次,每一个用于实现不同的功能。除了视图层次和图层树之外还有表示树(presentation tree)和渲染树(render tree)两个。这将分别在第7章“隐式动画”和第12章“速度调整”中进行讲解。
图层功能
那如果CALayer
仅仅是UIView
的内在工作的实现细节,为什么我们需要深入了解它呢?Apple确实恰当地提供了简单好用的UIView
借口,但因此我们就不需要去直接深穷Core Animation
本身的细节了吗?
这在某种程度下是正确的,如果出于简单的目的,我们并不需要真的去深究CALayer
,因为Apple使其可以通过用简单的高级API来间接作用于动画等的特性中。
但是这种便利性也带来了灵活性的丧失。如果你想做一些有别于常的东西,或者使用一些Apple并没有在UIView
的类接口中使用的特性,那你别无选择只能深入到Core Animation
之中去调整那些底层一点的选项。
我们说过图层并不能像UIView
那样处理触摸事件,那么他们能做什么视图所不能做的?下面列举了一些不能通过UIView
而仅能通过CALayer
使用的特性:
- 投射阴影、圆角和有色彩的边框
- 3D变形和位移
- 非矩形的边界
- 内容的透明遮罩
- 多步非线性动画
我们将在接下来的章节中分别介绍这些特性,但是现在让我们一起看看如何在一个应用中使用CALayer
。
使用图层
让我们从创建一个用于操作图层属性的简单项目开始。在Xcode中使用Single View Application模板创建一个新的iOS项目。
在屏幕中央创建一个小视图(大概200*200像素)。你可以根据你喜爱的方式,用程序代码或Interface Builder来实现这一切。只要确保你在你的view controller中包含了这一属性来让我们可以直接访问这一视图。我们将之命名为layerView
。
如果你运行这个项目,应该可以看见一个白色方块在一个浅灰色的背景中(如图1.3)。如果你并没有看到这个情形,你可能需要微调这个窗口或视图的背景颜色。
这看起来并不是非常赞,因此让我们加一点色彩,我们将在这个白色方块里面放一个小的蓝色方块。
我们可以通过简单地使用另一个UIView
并把它加为之前创建的视图的子视图来实现这一效果(通过代码或者Interface Builder),但这样并不会教给我们任何关于图层的知识。
因此,让我们创建一个CALayer
并把它做为我们白色视图的图层的子图层。尽管layer属性已经在UIView
类接口中暴露,但标准的Xcode的iOS项目模板并没有饮食Core Animation
的头文件,因此在我们给项目添加相应的框架之前,我们并不能高用任何方法或者访问这处个layer的任何属性。为了实现这一功能,首先添加QuartzCore
框架在程序的target's Build Phases中(见图1.4),并且在代码中import QuartzCore
在viewController.swift中。[1]
接下来,我们可以直接在代码中引用CALayer
以及它的属性、方法。在表1.1中,我们程序化创建了一个新的CALayer
,设置它的backgroundColor
属性,然后将它作为子图层添加到 layerView
中的图层中。(这个代码假设我们通过Interface Buiilder创建了这个视图并且早已经链接到layerView
出口。)图1.5显示了结果。
CALayer backgroundColor
属性是CGColorRef
类型的,并不是像UIView
类的backgroundColor
一样是UIColor
类型的,所以当设置颜色时我们需要使用UIColor
对象的CGColor
属性。如果你喜欢你也可以直接通过使用Core Graphics
方法来直接创建一个CGColor
,但使用UIColor
可以避免当你不再需要使用这个颜色手动释放它的麻烦。
表1.1 向视图添加一个蓝色的子图层
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var layerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// 创建子图层
let blueLayer = CALayer()
blueLayer.frame = CGRectMake(50.0, 50.0, 100.0, 100.0)
blueLayer.backgroundColor = UIColor.blueColor().CGColor
// 加入到白色的view中
self.layerView.layer.addSublayer(blueLayer)
}
}
一个视图只有唯一个主图层(自动创建的)但可以拥有无数的附加图层。正如表1.1所展示的,你可以显式地创建独立的图层并把它们作为子图层加入到这个视图的主图层中。尽管可以通过这种方式添加图层,但更多情况下你只需要使用到视图和它们的主图层,并不需要手动创建额外的图层。
在Mac OS 10.8之前,一个显著的缺点就是使用基于图层的视图层次而不是每个视图拥有一个独立的CALayer
树结构。但iOS中的轻量级UIView
类在使用图层时几乎没有不好的影响。(在Mac OS 10.8中NSView
的表现同样有了巨大的进步。)
使用基于图层的视图结构而非由有组织的CALayer
的优点是当你仍需要访问所有的底层CALayer
特性时,你不会丢失由UIView
类提供的高层的API(例如自动调整大小, 自动布局以及事件处理)。
但你可能还是想在一个现实中的应用里使用用有组织的CALayer
而非基于图层的视力结构,其原因有:
- 你可能要写一些同样需要运行于Mac中的跨平台的代码。
- 你可能要使用许多
CALayer
的子类(第6章“特定图层”)而且不想创建新的UIView
的子类去管理它们。 - 你可能在做高性能的工作,而任何一点多余的
UIView
对象会导致不可忽略的差异(尽管通常在这种情况下,你可能会使用例如OpenGL的工具来绘制)。
但这些情况都很少见,通常情况下,基于图层的视图往往更容易使用。
总结
这个章节讲述了图层树——这个平行存在于UIView
层次结构下的CALayer
对象的层次结构,用于构建iOS接口。我们也创建了我们自己的CALayer
并将之添加到图层树中作为我们的实验。
在第2章“主图像”中,我们将讲解CALayer
的主图像以及Core Animation
提供的操作其如何显示的属性。
-
译者使用的是Xcode6.4和Swift,并不需要这一步骤。因为已经
import UIKit
无需再import QuartzCore
。 ↩