0%

Core Animation Essentials(Core Animation Programming Guide)

Core Animation Essentials,主要讲了 Core Animation 的一些基础知识。

讲了一个点击图片 item 有各种效果的 demo 。

  1. 点击动画变大 (change bound)
  2. shrink and grow (UIKit 定时器改变 bound , CA 可以用 CAMedia timing protocol and repeated animation)
  3. 边框闪烁,颜色变化 (layer.border color is animation property, repeated animation)
  4. flip 到中心然后回到原处 (2.5D perspective transform)
  5. 边框粒子闪烁效果

然后后面就是讲 Core Animation 一些基础,所以下面就把Core Animation Programming Guide的内容大致过一下。

Introduction

Core Animation 是 iOS 和 OS X 上可用的图形渲染和动画基础结构,可用于为视图和应用程序的其他可视元素设置动画。 使用它,绘制动画的每个帧所需的大部分工作,都是它完成的。 我们所要做的就是配置一些动画参数(例如起点和终点)并告诉 Core Animation 启动。 Core Animation 完成剩下的工作,将大部分实际绘图工作交给图形硬件以加速渲染。 这种自动图形加速可以实现高帧速率和流畅的动画,而不会给 CPU 带来负担并降低应用程序的速度。 它大致有以下相关功能。

  • Core Animation Manages Your App’s Content, Core Animation 本身不是绘图系统。 它是一种用于在硬件中合成和操作应用内容的基础架构。 此基础结构的核心是图层对象(layer objects),可以使用它来管理和操作内容。 layer 将内容捕获到可以由图形硬件轻松操作的位图(bitmap)中。 在大多数应用程序中,图层用作管理视图内容的方式,但也可以根据需要创建独立图层。
  • Layer Modifications Trigger Animations, layer 相关属性的改变会导致创建隐式动画。 如果我们想要更好的控制,可以使用显式动画。
  • Layers Can Be Organized into Hierarchies, 可以像 UIView 一样排列创建父子关系(parent-child relationships)。 附加到 view 的一组 layer 的层次结构**镜像(mirror)**出相应的 view 层次结构; 也可以将独立 layer 添加到 layer 层次结构中。
  • Actions Let You Change a Layer’s Default Behavior, 隐式动画是由操作对象(action objects)实现的,它是实现预定义接口的通用对象。 Core Animation 使用操作对象来实现通常与图层关联的默认动画集。 我们可以创建自己的操作对象来实现自定义动画,也可以使用它们来实现其他类型的行为。 然后,将操作对象分配给图层的某个属性。当该属性更改时,Core Animation 将检索操作对象并告诉它执行其操作。(ps: session 2011.421 P25 也有讲到)

Core Animation Basics

Core Animation 提供了一个通用系统,用于动画视图和应用程序的其他可视元素。 它不是取代应用程序的视图。相反,它是一种与视图集成的技术,可为动画内容提供更好的性能和支持。 它通过将视图内容缓存到可以由图形硬件直接操作的位图来实现此行为。 在某些情况下,此缓存行为可能需要我们重新考虑如何呈现和管理应用程序的内容,但大多数时候使用 Core Animation 时却不知道它存在。除了缓存视图内容之外,它还可以动画

Layers Provide the Basis for Drawing and Animations

图层对象(layer object)是在 3D 空间中的 2D 表面,是使用 Core Animation 的核心。跟视图一样,它管理有关其曲面几何,内容和视觉属性的信息。但跟视图不同的是,图层不会定义自己的外观,仅管理位图周围的状态信息。位图本身可以是视图绘制本身的结果,或者是指定的固定图像的结果。 因此,在应用中使用的主要图层被视为模型对象(model objects),因为它们主要管理数据。 这个概念很重要,因为它会影响动画的行为。 (ps: layer 管理位图相关的状态信息)

The Layer-Based Drawing Model

大多数图层都不会在应用中进行任何实际绘图。 相反,图层会捕获应用提供的内容,并将其缓存在位图中,有时也称为后备存储(backing store)。 当随后更改图层的属性时,所做的只是更改与图层对象关联的状态信息。 当更改触发动画时, Core Animation 会将图层的位图和状态信息传递给图形硬件,图形硬件会使用新信息渲染位图,如下图所示:

Figure 1-1  How Core Animation draws content

硬件中操作位图会产生比在软件中更快。 (ps: 更改 layer 的属性或者触发动画时, Core Animation 只会更改相关联的状态信息,而不是图层,更高效。)

因为它操纵静态位图,所以基于图层(layer-based)的绘图与更传统的基于视图的绘图技术显着不同。 使用基于视图的绘图时,对视图本身的更改通常会导致调用视图的 drawRect: 方法以使用新参数重绘内容。 但是以这种方式绘制是很昂贵的,因为它是在主线程上使用 CPU 完成的。 Core Animation 通过在硬件中操纵缓存的位图来实现相同或类似的效果,尽可能避免这种费用。 (ps: drawRect: 会比较耗时,相对于 Layer-Based 操作静态位图)

Layer-Based Animations

图层对象的数据和状态信息 与 屏幕上该图层内容的可视化表示 分离。这种解耦使 Core Animation 成为一种介入自身的方法,并将从旧状态值到新状态值的变化设置为动画。

在动画过程中, Core Animation 会在硬件中完成所有逐帧绘图(frame-by-frame drawing)。 (ps: 硬件中绘制,这里应该是讲的 Render Server 中的 Core Animation 在 GPU 中绘制, session 2014.419 P34 也有讲到)

Layer Objects Define Their Own Geometry

图层的一个工作就是管理其内容的可视几何体。 可视几何体包含有关该内容边界,其在屏幕上的位置以及该层是否以任何方式旋转,缩放或变换的信息。(ps: 只要跟位置、大小相关的都与可视几何有关。) 与视图一样,图层具有 frame 和 bounds 属性,可以使用它们来定位图层及其内容。 图层还具有视图不具有的其他属性,例如 anchor point ,用于定义操作发生的点。 指定图层几何图形的某些方面的方式也与为视图指定信息的方式不同(ps: 这里应该主要就是讲 anchor point 吧)。

Layers Use Two Types of Coordinate Systems

图层使用基于点(point-based)的坐标系和单位(unit)坐标系来指定内容的位置。 使用哪种坐标系取决于所传达的信息类型。 指定直接映射到屏幕坐标的值 或 必须相对于另一个图层指定的值时使用基于点的坐标,例如图层的 position 属性。 当值不应与屏幕坐标相关联时使用单位坐标,因为它与某个其他值相关(ps: 后面紧接着一句有提到)。 例如,图层的 anchorPoint 属性指定相对于图层本身边界的点,它可以更改。

基于点的坐标最常见的用途是指定图层的大小和位置,使用图层的 bounds 和 position 属性。尽管图层还有 frame 属性,它实际上 bounds 和 position 属性的值派生的,并且使用频率较低。

图层 bounds 和 frame 的方向始终与底层平台的默认方向匹配。

  • iOS(top-left corner)
  • OS X(bottom-left corner)

如图
The default layer geometries for iOS and OS X

anchorPoint 是使用单位坐标系指定的几个属性之一。 Core Animation 使用单位坐标 来表示 在图层 size 更改时其值可能会更改的属性。 可以将单位坐标视为指定总可能值的百分比。 单位坐标空间中的每个坐标的范围都为 0.0 到 1.0 。 例如,沿 x 轴,左边缘位于坐标 0.0 ,右边缘位于坐标 1.0 。 沿 y 轴,单位坐标值的方向根据平台而变化,如图

The default unit coordinate systems for iOS and OS X

所有坐标值(无论是点还是单位坐标)都指定为浮点数。 使用浮点数可以指定可能介于正常坐标值之间的精确位置。 使用浮点值很方便,特别是在打印期间或绘制到 Retina 显示器时,其中一个点可能由多个像素表示。 浮点值允许您忽略基础设备分辨率,只需以您需要的精度指定值。 (ps: 浮点值的好处)

Anchor Points Affect Geometric Manipulations

position 和 transform 都受到 anchorPoint 的影响。

见下图
How the anchor point affects the layer’s position property

How the anchor point affects layer transformations

Layers Can Be Manipulated in Three Dimensions

每个图层都有两个变换矩阵,可以使用它们来操纵图层及其内容。

  • transform, 指定要应用于图层及其嵌入子图层的变换。
  • sublayerTransform, 仅适用于子图层的其他转换,最常用于向场景内容添加透视视觉效果。

通过将坐标值乘以数字矩阵来做变换工作,以获得表示原始点的变换版本的新坐标。 如图
Converting a coordinate using matrix math

还列举了各种常用的变换矩阵。如图
Matrix configurations for common transformations

Layer Trees Reflect Different Aspects of the Animation State

使用 Core Animation 的应用程序有三组图层对象。 每组图层对象在使应用内容显示在屏幕上时具有不同的作用:

  • layer tree(图层树), 应用与之交互的对象,它存储动画的目标值,当更改 layer 的属性内容时,就是用的它。
  • presentation tree(呈现树), 正在运行的动画的 in-flight values(时刻都会变换的,它表示动画的当前值),我们不应该改变它的值,相反而是获取这些值,然后开始新的动画。
  • render tree(渲染树), 它执行实际动画,并且是 Core Animation 的私有动画。

每组图层对象都组织成一个分层结构,就像应用程序中的视图一样,这是伴随着视图的。

可以将与视图无关的图层添加到图层层次中,好处就是针对不需要视图的所有开销的内容,优化应用程序的性能(Q: 具体是指哪些开销呢?)。

对于图层树中的每个对象,在呈现树和渲染树中都有一个匹配的对象。 应用程序主要使用图层树中的对象,但有时可能访问呈现树中的对象(用 presentationLayer 属性获取),因为我们想获取动画的当前值。

注意:只有动画在进行中(in flight)时,才应该访问呈现树中的对象。不然没有意义。此行为与图层树不同,图层树始终反映代码设置的最后一个值,并且等效于动画的最终状态。(因为它实时反映当前的值,所以 explicit animation 的时候要设置最终的值,不然动画完成以后会回到初始值,以为没做任何改变。)

The Relationship Between Layers and Views

图层不能替代应用程序的视图 - 也就是说,无法仅基于图层对象创建可视化界面。 图层为视图提供基础结构。 具体而言,在执行此操作时,图层可以更轻松、更有效地绘制视图内容并为其设置动画并保持较高的帧速率。 但是,图层有很多事情没有做。 图层不处理事件,绘制内容(draw content),参与响应链或执行许多其他操作。 出于这个原因,每个应用程序必须仍然有一个或多个视图来处理这些类型的交互。

在 iOS 中,每个视图都由相应的图层对象支持(backed),被称为图层支持的视图(layer-backed view)。在图层支持的视图中,系统负责创建底层图层对象并保持该图层与视图同步。

注意:对于图层支持的视图,建议尽可能操纵视图而不是其图层。 在 iOS 中,视图只是图层对象的一个薄包装,因此对图层所做的任何操作通常都可以正常工作。 但是在 iOS 和 OS X 中都存在这样的情况:操纵图层而不是视图可能无法产生预期的结果。 只要有可能,本文档指出了这些陷阱,并试图提供方法来帮助您解决这些问题。 (ps: 操作视图,而是图层。)

除了与视图关联的图层外,还可以创建没有相应视图的图层对象。可以将这些独立图层对象嵌入到应用程序中的任何其他图层对象中,包括与视图关联的对象。 通常使用独立层对象作为特定优化路径的一部分。 例如,如果要在多个位置使用相同的图像,可以将图像加载一次,并将其与多个独立图层对象关联,然后将这些对象添加到图层树。 然后,每个层都引用源图像,而不是尝试在内存中创建自己的图像副本。

Setting Up Layer Objects

图层对象是使用 Core Animation 执行的所有操作的核心。 图层管理应用程序的可视内容,并提供 用于修改该内容的样式和视觉外观的 选项

Enabling Core Animation Support in Your App

在 iOS 应用程序中,始终启用 Core Animation ,每个视图都由一个图层支持。

Changing the Layer Object Associated with a View

默认情况下,图层支持的视图会创建 CALayer 类的实例,在大多数情况下,可能不需要不同类型的图层对象。 选择不同的图层类可能会以简单的方式提高性能或支持特定类型的内容。 例如, CATiledLayer 类被优化用于以有效的方式显示大图像。(ps:专人专项)

Changing the Layer Class Used by UIView

可以通过覆盖视图的 layerClass 方法并返回不同的类对象来更改 iOS 视图使用的图层类型。默认是 CALayer 对象。 但是可能在某些情况下,不同的图层类更合适。比如:

  • 视图使用 Metal 或 OpenGL ES 绘制内容,在这种情况下,应该使用 CAMetalLayer 或 CAEAGLLayer 对象。
  • 有一个专门的图层类可以提供更好的性能。(专人专项)
  • 希望利用一些专门的 Core Animation 图层类,例如粒子发射器(particle emitters)或复制器(replicators)。

改变图层的代码如下,在显示之前,视图调用 layerClass 方法并使用返回的类为自己创建新的图层对象。 创建后,无法更改视图的图层对象。

1
2
3
+ (Class) layerClass {
return [CAMetalLayer class];
}

Different Layer Classes Provide Specialized Behaviors

列举了不同的 Layer 类型提供的特定行为。

  • CAEmitterLayer, 用于实现基于Core Animation的粒子发射器系统。
  • CAGradientLayer, 颜色渐变。
  • CAMetalLayer, Metal 渲染内容。
  • CAEAGLLayer/CAOpenGLLayer, OpenGL ES (iOS) or OpenGL (OS X) 渲染内容。
  • CAReplicatorLayer, 复制器。
  • CAScrollLayer, 滚动。
  • CAShapeLayer, 用于绘制立方贝塞尔样条曲线。形状图层有利于绘制基于路径的形状,因为它们总是会产生清晰的路径,而不是绘制到图层的后备存储中的路径,这在缩放时看起来不太好。 但是,清晰的结果确实涉及在主线程上呈现形状并缓存结果。
  • CATextLayer, 纯/富 文本。
  • CATiledLayer, 用于管理大图像,可以将其划分为较小的图块并单独渲染,并支持放大和缩小内容。
  • CATransformLayer, 用于呈现真正的 3D 图层层次结构,而不是由其他图层类实现的展平图层层次结构。
  • QCCompositionLayer, 用于渲染Quartz Composer合成。 (仅限OS X)

Providing a Layer’s Contents

图层是 管理应用提供内容的 数据对象。 图层的内容由 包含要显示的可视数据的位图 组成。

  • 将图像对象(image object)直接分配给图层对象的 contents 属性。 (此技术最适用于从未或很少更改的图层内容。)
  • 将委托对象(delegate object)分配给图层,让委托绘制图层的内容。 (此技术最适用于可能会定期更改并可由外部对象(如视图, UIView )提供的图层内容。)
  • 定义图层子类(layer subclass)并覆盖其绘图方法之一以自己提供图层内容。 (如果必须创建自定义图层子类或者如果要更改图层的基本绘图行为,则此技术是合适的。)

自己创建图层对象时,唯一需要担心为图层提供内容的时间。 如果应用程序只包含图层支持的视图,则不必担心使用上述任何技术来提供图层内容,图层支持的视图以最有效的方式自动提供其关联图层的内容。

Using an Image for the Layer’s Content

由于图层只是用于管理位图图像的容器,因此可以将图像直接指定给图层的内容属性。
图层使用我们直接提供的图像对象,不会尝试创建自己的图像副本。 如果在应用的多个位置使用相同的图像,此行为可以节省内存。

赋值的时候 CGImageRef 类型,记得提供一个分辨率与本机设备分辨率相匹配的图像(调整 contentsScale 属性)。

Using a Delegate to Provide the Layer’s Content

使用委托对象在显示时,图层调用委托的方法来提供所需的内容:

  • 如果委托对象实现了 displayLayer: 方法,那么它实现负责创建位图并将其分配给图层的 contents 属性。
  • 如果委托对象实现了 drawLayer:inContext: 方法,则 Core Animation 会创建一个位图,创建一个图形上下文(graphics context)以绘制到该位图,然后调用委托方法来填充该位图。 所有委托方法都要绘制到提供的图形上下文中

委托对象实现了 displayLayer: 或者 drawLayer:inContext: 方法,如果两者都实现了,只会调用 displayLayer: 方法。

覆盖 displayLayer: 方法 最适合 应用程序更喜欢加载或创建它想要显示的位图的情况

1
2
3
4
5
6
7
8
9
10
11
12
// Setting the layer contents directly
- (void)displayLayer:(CALayer *)theLayer {
// Check the value of some state property
if (self.displayYesImage) {
// Display the Yes image
theLayer.contents = [someHelperObject loadStateYesImage];
}
else {
// Display the No image
theLayer.contents = [someHelperObject loadStateNoImage];
}
}

如果没有预渲染图像或辅助对象来创建位图,则委托可以使用 drawLayer:inContext: 方法动态绘制内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Drawing the contents of a layer
- (void)drawLayer:(CALayer *)theLayer inContext:(CGContextRef)theContext {
CGMutablePathRef thePath = CGPathCreateMutable();

CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
CGPathAddCurveToPoint(thePath,
NULL,
15.f,250.0f,
295.0f,250.0f,
295.0f,15.0f);

CGContextBeginPath(theContext);
CGContextAddPath(theContext, thePath);

CGContextSetLineWidth(theContext, 5);
CGContextStrokePath(theContext);

// Release the path
CFRelease(thePath);
}

对于具有自定义内容的图层支持视图,应该继续覆盖视图的方法来进行绘制。 图层支持的视图自动使其自身成为其图层的委托,并实现所需的委托方法,不应更改该配置。 相反,应该实现视图的 drawRect: 方法来绘制内容。 (ps: layer-backed custom view 就是其 layer 的代理)。

Providing Layer Content Through Subclassing

如果要实现自定义图层类,则可以覆盖图层类的绘图方法以执行任何绘图。 图层对象本身生成自定义内容的情况并不常见,但图层当然可以管理内容的显示。例如, CATiledLayer 类通过将大图像分成可以单独管理和呈现的较小图块 来管理大图像。因为只有图层具有关于在任何给定时间需要渲染哪些图块的信息,所以它直接管理绘图行为

子类化时,可以使用以下任一技术绘制图层的内容:

  • 覆盖图层的 display 方法,并使用它直接设置图层的 contents 属性。
  • 覆盖图层的 drawInContext: 方法,并使用它绘制到提供的图形上下文中。

覆盖哪种方法取决于在绘图过程中的控制程度。 display 方法是更新图层内容的主要入口点,因此覆盖该方法可以完全控制该过程。覆盖 display 方法还意味着我们负责创建要分配给 contents 属性的 CGImageRef 。如果只想绘制内容(或让图层管理绘图操作),可以改写 drawInContext: 方法,让图层创建后备存储。

Tweaking the Content You Provide

将图像指定给图层的 contents 属性时,图层的 contentsGravity 属性确定如何处理该图像以适合当前边界。 默认情况下,如果图像大于或小于当前边界,则图层对象会缩放图像以适合可用空间。 如果图层边界的宽高比不同于图像的宽高比,则可能导致图像失真。 可以使用 contentsGravity 属性来确保以尽可能最佳的方式呈现内容。

分配给 contentsGravity 属性的值分为两类:

  • position-based gravity constants, 基于位置的重力常数允许将图像固定到图层边界矩形的特定边或角,而不缩放图像。(见下图 Position-based gravity constants for layers)
  • scaling-based gravity constants, 基于缩放的重力常数允许使用多个选项之一来拉伸图像,其中一些选项保留宽高比,而其中一些选项不保留。(见下图 Scaling-based gravity constants for layers)

Position-based gravity constants for layers

Scaling-based gravity constants for layers

只有 kCAGravityResize 不保留原图像的宽高比。

Working with High-Resolution Images

图层对底层设备屏幕的分辨率没有任何固有的了解,图层只是存储指向位图的指针,并在给定可用像素的情况下以最佳方式显示它。如果将图像指定给图层的内容属性,则必须通过将图层的 contentsScale 属性设置为适当的值来告知 Core Animation 图像的分辨率。该属性的默认值为 1.0,适用于要在标准分辨率屏幕上显示的图像。如果图像用于 Retina 显示,请将此属性的值设置为 2.0 。

只有在直接为图层指定位图时,才需要更改 contentsScale 属性的值。 UIKit 和 AppKit 中的 图层支持的视图 会根据屏幕分辨率和视图管理的内容 自动将其图层的比例因子设置为适当的值。

Adjusting a Layer’s Visual Style and Appearance

图层对象内置了视觉装饰(visual adornments),例如边框和背景颜色,可用于补充图层的主要内容。 由于这些视觉装饰不需要进行任何渲染,因此可以在某些情况下将图层用作独立实体。 我们所要做的就是在图层上设置属性,图层处理必要的图形,包括任何动画。 有关这些视觉装饰如何影响图层外观的其他说明,请参阅”#Layer Style Property Animations#”。

Layers Have Their Own Background and Border

Adding a border and background to a layer
background color 在图层的内容图像后面呈现, border 呈现在该图像的顶部。

注意:请注意 Core Graphics 处理图案图像(pattern images)的渲染,并使用其标准坐标系进行处理,该坐标系与 iOS 中的默认坐标系不同。 (ps: 注意 Core Graphics 使用的坐标系与 iOS 中的 y 轴相反。)

如果将图层的背景颜色设置为不透明颜色(opaque color),请考虑将图层的 opaque 属性设置为 YES 。 这样做可以在屏幕上合成图层时提高性能,并且无需图层的后备存储来管理 Alpha 通道但是,如果图层也具有非零角半径(nonzero corner radius),则不能将图层标记为不透明。

Layers Support a Corner Radius

角半径(corner radius)是一种视觉装饰,可以**遮盖(mask)**图层边界矩形的一部分角落,以允许底层内容显示,如下图所示。

A corner radius on a layer

因为它涉及应用透明蒙版,所以角半径不会影响图层内容属性中的图像,除非 masksToBounds 属性设置为 YES 。但是,角半径始终会影响图层的 Background 和 Border 的绘制方式。

设置 layer.cornerRadius 即可完成这种效果。

Layers Support Built-In Shadows

CALayer 类有几个用于配置阴影效果的属性。 阴影使图层看起来好像浮在其底层内容之上,从而增加了图层的深度。 默认情况下,图层阴影的不透明度值设置为 0 ,这有效地隐藏了阴影。将不透明度更改为非零值会导致 Core Animation 绘制阴影。由于默认情况下阴影直接位于图层下方,因此可能还需要先更改阴影的偏移量,然后才能看到它。但重要的是要记住,为阴影指定的偏移量是使用图层的原生坐标系统应用的,这在 iOS 和 OS X 上是不同的。

Applying a shadow to a layer

实现向下的阴影, iOS y 为正值,而 OS X 则为负值。

向图层添加阴影时,阴影是图层内容的一部分,但实际上是在图层的边界矩形之外。 因此,如果为图层启用 masksToBounds 属性,则会在边缘周围剪切阴影效果, 如果图层包含任何透明内容,这可能会导致奇怪的效果,即图层正下方的阴影部分仍然可见,但超出图层的部分则不会。 如果想要阴影但又想使用边界遮罩,则使用两个图层而不是一个图层。 将蒙版应用于包含内容的图层,然后将该图层嵌入到启用了阴影效果的完全相同大小的第二层内。 (ps: 阴影嵌套圆角,需要写个 demo 处理玩一下)

Adding Custom Properties to a Layer

CAAnimation 和 CALayer 类都扩展了 键值编码(key-value coding)约定以支持自定义属性。 可以使用此行为将数据添加到图层,并使用自定义键检索它。 甚至可以将操作与自定义属性关联,以便在更改属性时执行相应的动画。 可以看 “#Key-Value Coding Compliant Container Classes#” 和 “#Changing a Layer’s Default Behavior#”。

Printing the Contents of a Layer-Backed View

在打印期间,层根据需要重新绘制其内容以适应打印环境。 Core Animation 在渲染到屏幕时通常依赖于缓存的位图,而在打印时会重绘该内容。 特别是,如果图层支持的视图使用 drawRect: 方法提供图层内容,则 Core Animation 会在打印期间再次调用 drawRect: 以生成打印的图层内容。

Animating Layer Content

Core Animation 提供的基础架构可以轻松创建应用层的复杂动画,并通过扩展创建拥有这些层的任何视图。 示例包括更改图层框架矩形的大小(frame),更改其在屏幕上的位置(position),应用旋转变换(rotation transform)或更改其不透明度(opacity)。 使用 Core Animation ,启动动画通常只需更改属性即可(隐式动画),但也可以创建动画并明确设置动画参数(显式动画)。

关于创建更高级的动画,情况”#Advanced Animation Tricks#”。

Animating Simple Changes to a Layer’s Properties

可以根据需要隐式或显式地执行简单的动画。 隐式动画使用默认的计时和动画属性来执行动画,而显式动画则要求使用动画对象自己配置这些属性。 因此,隐式动画非常适合想要在没有大量代码的情况下进行更改的情况,并且默认时间适合业务需求。

简单的动画涉及更改图层的属性,让 Core Animation 随时间动画显示这些更改。 图层定义了许多影响图层可见外观的属性。 更改其中一个属性是一种为外观更改设置动画的方法。 例如,将图层的不透明度从 1.0 更改为 0.0 会导致图层淡出并变为透明。

要触发隐式动画,所要做的就是更新图层对象的属性。 在图层树中修改图层对象时,这些对象会立即反映这些更改。 但是,图层对象的视觉外观不会立即更改(ps: 应该是需要等待下个 runloop)。 相反, Core Animation 使用所做的更改作为触发器来创建和安排一个或多个隐式动画以供执行。 因此,进行类似于代码清单 3-1 中的更改会导致 Core Animation 创建动画对象,并安排该动画从下一个更新周期开始运行。

1
2
// Listing 3-1  Animating a change implicitly
theLayer.opacity = 0.0;

若要使用动画对象显式进行相同更改,请创建 CABasicAnimation 对象并使用该对象配置动画参数。 可以在将动画添加到图层之前设置动画的开始和结束值,更改持续时间或更改任何其他动画参数。 代码清单 3-2 显示了如何使用动画对象淡出图层。 创建对象时,指定要设置动画的属性的键路径(key path),然后设置动画参数。 要执行动画,可以使用 addAnimation:forKey: 方法将其添加到要设置动画的图层。

1
2
3
4
5
6
7
8
9
// Listing 3-2  Animating a change explicitly
CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 1.0;
[theLayer addAnimation:fadeAnim forKey:@"opacity"];

// Change the actual data value in the layer to the final value.
theLayer.opacity = 0.0;

提示:创建显式动画时,建议始终为动画对象的 fromValue 属性指定值。 如果未指定此属性的值, Core Animation 将使用图层的当前值作为起始值。 如果已将属性更新为其最终值,则可能无法获得所需的结果。

更新图层对象的数据值的隐式动画不同,显式动画不会修改图层树中的数据,显式动画仅生成动画。在动画结束时, Core Animation 从图层中移除动画对象,并使用其当前数据值重绘图层。如果希望显式动画的更改是永久性的,则还必须更新图层的属性,如上例所示。

隐式和显式动画通常在当前运行循环(run loop)周期结束后开始执行,并且当前线程必须具有运行循环才能执行动画。如果更改多个属性,或者向图层添加多个动画对象,则会同时对所有这些属性更改进行动画处理。例如,可以通过同时配置两个动画来淡化图层,同时将其移出屏幕。但是,还可以将动画对象配置为在特定时间启动。有关修改动画定时的更多信息,请参阅”#Customizing the Timing of an Animation#”。 (session 2011.421 P20 也有提到)

Using a Keyframe Animation to Change Layer Properties

基于属性的动画(property-based animation)将属性从起始值更改为结束值,而 CAKeyframeAnimation 对象允许以不是线性的方式设置一组目标值的动画。 关键帧动画由一组目标数据值和每个值到达的时间组成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Listing 3-3  Creating a bounce keyframe animation (用 path 来处理)

// create a CGPath that implements two arcs (a bounce)
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,74.0,74.0);
CGPathAddCurveToPoint(thePath,NULL,74.0,500.0,
320.0,500.0,
320.0,74.0);
CGPathAddCurveToPoint(thePath,NULL,320.0,500.0,
566.0,500.0,
566.0,74.0);

CAKeyframeAnimation * theAnimation;

// Create the animation object, specifying the position property as the key path.
theAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
theAnimation.path=thePath;
theAnimation.duration=5.0;

// Add the animation to the layer.
[theLayer addAnimation:theAnimation forKey:@"position"];

Specifying Keyframe Values

api 的相关用法,具体看文档吧。

Specifying the Timing of a Keyframe Animation

api 的相关用法,具体看文档吧。

Stopping an Explicit Animation While It Is Running

动画通常会一直运行直到完成,但如果需要,可以使用以下技术之一提前停止:

  • 要从图层中删除单个动画对象,请调用图层的 removeAnimationForKey: 方法以删除动画对象。此方法使用传递给 addAnimation:forKey: 方法的键来标识动画。 key 不能为 nil。
  • 要从图层中删除所有动画对象,请调用图层的 removeAllAnimations 方法。 此方法立即删除所有正在进行的动画,并使用其当前状态信息重新绘制图层。

注意:无法直接从图层中删除隐式动画。

从图层中删除动画时, Core Animation 会通过使用当前值重新绘制图层。因为当前值通常是动画的结束值,这可能导致图层的外观突然跳跃。如果希望图层的外观保持在动画最后一帧的位置,则可以使用呈现树中的对象检索这些最终值并将其设置在图层树中的对象上。

Animating Multiple Changes Together

如果要同时将多个动画应用于图层对象,可以使用 CAAnimationGroup 对象将它们组合在一起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Listing 3-4  Animating two animations together (同时修改边框宽度和颜色)

// Animation 1
CAKeyframeAnimation* widthAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderWidth"];
NSArray* widthValues = [NSArray arrayWithObjects:@1.0, @10.0, @5.0, @30.0, @0.5, @15.0, @2.0, @50.0, @0.0, nil];
widthAnim.values = widthValues;
widthAnim.calculationMode = kCAAnimationPaced;

// Animation 2
CAKeyframeAnimation* colorAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderColor"];
NSArray* colorValues = [NSArray arrayWithObjects:(id)[UIColor greenColor].CGColor,
(id)[UIColor redColor].CGColor, (id)[UIColor blueColor].CGColor, nil];
colorAnim.values = colorValues;
colorAnim.calculationMode = kCAAnimationPaced;

// Animation group
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:colorAnim, widthAnim, nil];
group.duration = 5.0;

[myLayer addAnimation:group forKey:@"BorderChanges"];

将动画分组在一起的更高级方法是使用事务对象(transaction object)。 事务提供了更大的灵活性,通过允许创建嵌套的动画集并为每个动画分配不同的动画参数。 有关如何使用事务对象的信息,请参阅”#Explicit Transactions Let You Change Animation Parameters#”。

Detecting the End of an Animation

Core Animation 支持检测动画开始或结束的时间。这些通知是进行与动画相关的任何内务(housekeeping)处理任务的好时机。例如,可以使用开始通知来设置一些相关的状态信息,并使用相应的结束通知来拆除该状态。

有两种不同的方式可以通知动画的状态:

  • 使用 setCompletionBlock: 方法将完成块(completion block)添加到当前事务。当事务中的所有动画完成后,事务将执行完成块。
  • 将委托分配给 CAAnimation 对象并实现 animationDidStart: 和 animationDidStop:finished: 委托方法。

如果要将两个动画链接在一起,以便在另一个完成时启动另外一个动画,请不要使用动画通知。而是使用动画对象的 beginTime 属性在所需的时间启动每个属性。要将两个动画链接在一起,请将第二个动画的开始时间设置为第一个动画的结束时间。有关动画和计时值的更多信息,请参阅”#Customizing the Timing of an Animation#”。 (session 2011.421 P51 也有讲到。三个气球依次跳的例子)

How to Animate Layer-Backed Views

如果图层属于图层支持的视图,则建议创建动画的方法是使用 UIKit 或 AppKit 提供的基于视图的动画接口(view-based animation interfaces)。 有一些方法可以使用 Core Animation 接口直接为图层设置动画,但是如何创建这些动画取决于目标平台。

Rules for Modifying Layers in iOS

由于 iOS 视图始终具有图层,因此 UIView 类本身直接从层对象派生其大部分数据。因此,对图层所做的更改也会自动反映在视图对象中(ps: 前面 “The Relationship Between Layers and Views” 也有提到)。 此行为意味着可以使用 Core Animation 或 UIView 接口进行更改。

如果要使用 Core Animation 类来启动动画,则必须从基于视图的动画块中发出所有 Core Animation 调用。 UIView 类默认禁用图层动画,但在动画块内重新启用它们。因此,在动画块之外所做的任何更改都不会生成动画。代码清单3-5显示了如何隐式更改图层的不透明度及其显式位置的示例。在此示例中, myNewPosition 变量预先计算并由块捕获。两个动画同时开始,但不透明度动画以默认时序运行,而位置动画以其动画对象中指定的时间运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
/// Listing 3-5  Animating a layer attached to an iOS view

[UIView animateWithDuration:1.0 animations:^{
// Change the opacity implicitly.
myView.layer.opacity = 0.0;

// Change the position explicitly.
CABasicAnimation* theAnim = [CABasicAnimation animationWithKeyPath:@"position"];
theAnim.fromValue = [NSValue valueWithCGPoint:myView.layer.position];
theAnim.toValue = [NSValue valueWithCGPoint:myNewPosition];
theAnim.duration = 3.0;
[myView.layer addAnimation:theAnim forKey:@"AnimateFrame"];
}];

Rules for Modifying Layers in OS X

OS X 先不管。

Remember to Update View Constraints as Part of Your Animation

如果使用基于约束(constraint-based layout)的布局规则来管理视图的位置,则必须删除可能会影响动画的任何约束,尤其是作为配置该动画的那一部分。 约束会影响对视图的位置或大小所做的任何更改。 它们还会影响视图与其子视图之间的关系。 如果要为这些项中的任何项设置动画,则可以删除约束,进行更改,然后应用所需的任何新约束。 (ps: 先移除约束,进行改变后,在添加新的约束) 具体的去看 Auto Layout Guide

Building a Layer Hierarchy

大多数情况下,在应用中使用图层的最佳方法是将它们与视图对象结合使用。 但是,有时可能需要通过向其添加其他图层对象来增强视图层次结构。 可以在使用图层时使用图层提供更好的性能,或者让实现仅使用视图难以执行的功能。 在这些情况下,需要知道如何管理您创建的图层层次结构。

Arranging Layers into a Layer Hierarchy

Layer 层次结构在许多方面类似于 View 层次结构。 将一个图层嵌入另一个图层中,以在嵌入的图层(称为子图层)和父图层(称为超级图层)之间创建父子关系。 这种父子关系影响子层的各个方面。 例如,其内容位于其父级的内容之上,其位置相对于其父级的坐标系指定,并且它受应用于父级的任何变换的影响。

Adding, Inserting, and Removing Sublayers

  • Adding layers, “addSublayer:” 方法, “zPosition” 属性
  • Inserting layers, “insertSublayer:above: insertSublayer:atIndex: insertSublayer:below:” 方法,
  • Removing layers, “removeFromSuperlayer”方法,
  • Exchanging layers, “replaceSublayer:with:”方法,

在处理自己创建的图层对象时,可以使用上述方法。 不能用在图层支持的视图的图层。 但是,图层支持的视图的图层可以作为己创建的图层对象的父级。

Positioning and Sizing Sublayers

添加和插入子图层时,必须先设置子图层在屏幕上显示之前的大小和位置。 将子图层添加到图层层次结构后,可以修改子图层的大小和位置,但在创建图层时应养成设置这些值的习惯。 (ps: 养成先设置值再添加的好习惯)

重要:图层的宽高值一直用整数。

How Layer Hierarchies Affect Animations

某些父图层属性可能会影响应用于其子图层的任何动画的行为。一个这样的属性是 speed 属性,它是动画速度的乘数。默认情况下,此属性的值设置为 1.0 ,但将其更改为 2.0 会导致动画以原始速度的两倍运行,从而在一半的时间内完成。此属性不仅会影响为其设置的图层,还会影响该图层的子图层。这种变化也是倍增的,如果子层及其父层都具有 2.0 的速度,则子层上的动画以其原始速度的四倍运行。(ps: 父层的值影响子层。)

大多数其他图层更改 以可预测的方式 影响任何包含的子图层。例如,将旋转变换应用于图层会旋转该图层及其所有子图层。同样,更改图层的不透明度会更改其子图层的不透明度。 对图层大小的更改遵循”#Adjusting the Layout of Your Layer Hierarchies#”。

Adjusting the Layout of Your Layer Hierarchies

Core Animation 支持多种选项,用于调整子层的大小和位置以响应其超层的更改。 在 iOS 中,图层支持的视图的普遍使用使得层次结构的创建不那么重要;仅支持手动布局更新。 对于 OS X ,还有其他几个选项可以更轻松地管理图层层次结构。

仅当使用创建的独立图层对象构建图层层次结构时,图层级布局(Layer-level layout)才有意义。 如果应用的图层都与视图相关联,请使用基于视图的布局支持来更新视图的大小和位置以响应更改。 (ps: 只对独立的图层形成的图层关系才有意义,所以这一节对 iOS 意义不大,都忽略掉。)

Using Constraints to Manage Your Layer Hierarchies in OS X

Setting Up Autoresizing Rules for Your OS X Layer Hierarchies

Manually Laying Out Your Layer Hierarchies

Sublayers and Clipping

与视图不同,父图层不会自动剪切位于其边界矩形之外的子图层的内容。 相反,父层允许其子层默认显示完整。 但是,可以通过将图层的 masksToBounds 属性设置为 YES ,来重新启用剪切。

图层的剪切蒙版的形状包括图层的角半径(如果指定了一个,就是说裁剪的时候角半径还是留着的)。 下图显示了一个图层,该图层演示了 masksToBounds 属性如何影响具有圆角的图层。 当属性设置为 NO 时,即使子图层超出其父图层的边界,也会完整显示子图层。 将属性更改为 YES 会导致其内容被剪裁。
Clipping sublayers to the parent’s bounds

Converting Coordinate Values Between Layers

有时,可能需要将一个图层中的坐标值转换为另一个图层中同一屏幕位置的坐标值。 CALayer 类提供了一组简单的转换例程,可以将它们用于此目的:

  • convertPoint:fromLayer:
  • convertPoint:toLayer:
  • convertRect:fromLayer:
  • convertRect:toLayer:

除了转换 Point 和 Rect 值之外,还可以使用 convertTime:fromLayer:convertTime:toLayer: 方法在图层之间转换时间值。 每个层定义自己的本地时间空间,并使用该时间空间将动画的开始和结束与系统的其余部分同步。 这些时间空间默认是同步的;但是,如果更改一组图层的动画速度,则这些图层的时间空间会相应更改。 可以使用时间转换(time conversion)方法来考虑任何此类因素,并确保两个图层的时间同步。

1
2
// 参考代码, self.view 是 button 的 superView.superView....
var buttonInViewPoint = button.convert(self.view.frame.origin, to: self.view)

Advanced Animation Tricks

有许多方法可以配置基于属性(property-based)或关键帧(keyframe)的动画,以便完成更多操作。 需要一起或按顺序执行多个动画的应用程序,可以使用更高级的行为来同步这些动画的时序或将它们链接(chain)在一起。 还可以使用其他类型的动画对象来创建视觉过渡和其他有趣的动画效果。

Transition Animations Support Changes to Layer Visibility

顾名思义,过渡动画对象为图层创建动画视觉过渡。过渡对象(transition objects)最常见的用途是以协调的方式(coordinated manner)为一个图层的显示和另一个图层的消失设置动画。与基于属性的动画(动画更改图层的一个属性)不同,过渡动画操纵图层的缓存图像以创建 通过单独更改属性而难以或无法完成的 视觉效果。标准的过渡类型允许执行 reveal, push, move 或 crossfade 动画。在 OS X 上,还可以使用 Core Image 过滤器创建使用其他类型效果。

要执行过渡动画,要创建 CATransition 对象并将其添加到过渡中涉及的层上。 可以使用过渡对象指定要执行的过渡类型以及过渡动画的起点和终点。也不需要使用整个过渡动画,过渡对象允许动画时指定要使用的开始和结束进度值。这些值允许在其中点处开始或结束动画。

代码清单 5-1 显示了用于在两个视图之间创建动画 push 过渡的代码。在示例中, myView1 和 myView2 都位于同一父视图中的相同位置,但当前只能看到 myView1 。 push 过渡导致 myView1 向左滑动并淡出,直到隐藏,而 myView2 从右侧滑入并变为可见。更新两个视图的隐藏属性可确保在动画结束时两个视图的可见性都是正确的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Listing 5-1  Animating a transition between two views in iOS

CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
transition.duration = 1.0;

// Add the transition animation to both layers
[myView1.layer addAnimation:transition forKey:@"transition"];
[myView2.layer addAnimation:transition forKey:@"transition"];

// Finally, change the visibility of the layers.
myView1.hidden = YES;
myView2.hidden = NO;

当两个图层涉及同一个过渡时,可以为两个图层使用相同的过渡对象。 使用相同的转换对象还可以简化必须编写的代码。 但是,也可以使用不同的过渡对象,如果每个层的过渡参数不同,则肯定需要这样做。

Customizing the Timing of an Animation

时序(timing)是动画的重要组成部分,通过 Core Animation, 可以通过 CAMediaTiming 协议的方法和属性为动画指定精确的时序信息。两个核心动画类采用此协议。 CAAnimation 类采用它,以便可以在动画对象中指定时序信息。 CALayer 也采用它,以便可以为隐式动画配置一些与时序相关的功能,尽管包装这些动画的隐式事务对象通常提供优先的默认时序信息

在考虑时间和动画时,了解图层对象如何随时间工作非常重要。每个图层都有自己的本地时间,用于管理动画计时。通常,两个不同层的本地时间足够接近,可以为每个层指定相同的时间值,用户可能不会注意到任何内容。但是,图层的本地时间可以通过其父层或其自己的时序参数进行修改。例如,更改图层的速度(speed)属性会导致该图层(及其子图层)上的动画持续时间按比例更改。

为了帮助确保时间值适合给定图层, CALayer 类定义 convertTime:fromLayer:convertTime:toLayer: 方法。可以使用这些方法将固定时间值转换为图层的本地时间,或将时间值从一个图层转换为另一个图层。这些方法会考虑可能影响图层本地时间的媒体计时属性(media timing properties), 并返回可与其他图层一起使用的值。代码清单5-3显示了一个示例,应该定期使用该示例来获取图层的当前本地时间。 CACurrentMediaTime 函数是一个便捷函数,它返回计算机当前的时钟时间,该方法采用该时间并转换为图层的本地时间。

1
CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];

一旦在图层的本地时间中有时间值后,可以使用该值更新动画对象或图层的 与时序相关的 属性。使用这些时序属性,可以实现一些有趣的动画行为,包括:

  • 使用 beginTime 属性设置动画的开始时间。通常,动画在下一个更新周期开始。可以使用 beginTime 参数将动画开始时间延迟几秒钟。将两个动画链接在一起的方法是,将一个动画的开始时间设置为与另一个动画的结束时间相匹配。 如果要 延迟 动画的开始,可能还需要将 fillMode 属性设置为 kCAFillModeBackwards 。即使图层树中的图层对象包含不同的值,此填充模式也会使图层显示动画的起始值。如果没有此填充模式,将看到在动画开始执行之前跳转到最终值。其他填充模式也可用。
  • autoreverses 属性使动画在指定的持续时间内执行,然后返回到动画的起始值。可以将此属性与 repeatCount 属性组合,以在起始值和结束值之间来回动画。将 repeatCount 设置为自动回转动画的整数(例如 1.0)会导致动画停止在其起始值上。添加额外的半步(例如为1.5)会导致动画停止在其结束值上。
  • 将 timeOffset 属性与组动画一起使用,可以在以后的时间启动某些动画。 (ps: 见下面的代码清单 5-4 ,有用到 timeOffset)

Pausing and Resuming Animations

要暂停动画,可以利用层 采用 CAMediaTiming 协议并将图层动画的速度设置为 0.0。 将速度设置为零会暂停动画,直到将值更改回非零值。 代码清单5-4显示了一个简单的示例,说明如何在以后暂停和恢复动画。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Listing 5-4  Pausing and resuming a layer’s animations

-(void)pauseLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}

-(void)resumeLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}

Explicit Transactions Let You Change Animation Parameters

对图层所做的每项更改都必须是事务(transaction)的一部分。 CATransaction 类在适当的时间管理动画的创建和分组及其执行。 在大多数情况下,不需要创建自己的事务。 每当向其中一个图层添加显式或隐式动画时,Core Animation 都会自动创建隐式事务。但是,还可以创建显式事务以更精确地管理这些动画。(ps: 事务是管理动画的,很重要,下面禁用隐式动画也用到了显式事务;见 wwdc-2011-121 “CATransaction and when views get rendered”)

可以使用 CATransaction 类的方法创建和管理事务。 要启动(并隐式创建)新事务,请调用 begin 类方法; 要结束该事务,请调用 commit 类方法。 想 在这些调用之间的更改 成为事务的一部分。 例如,要更改图层的两个属性,可以使用代码清单5-5中的代码。

1
2
3
4
5
6
// Listing 5-5  Creating an explicit transaction

[CATransaction begin];
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];

使用事务的主要原因之一,是在显式事务的范围内,可以更改持续时间,计时功能和其他参数。 还可以为整个事务分配完成块(completion block),以便在动画组完成时通知应用。 更改动画参数需要使用 setValue:forKey: 方法修改事务字典中的相应键。 例如,要将默认持续时间更改为 10 秒,将更改 kCATransactionAnimationDuration 键,如代码清单5-6所示。

1
2
3
4
5
6
7
// Listing 5-6  Changing the default duration of animations

[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
forKey:kCATransactionAnimationDuration];
// Perform the animations
[CATransaction commit];

还可以嵌套处理。 只有在为最外层事务提交更改后, Core Animation 才会开始关联的动画。 (ps: begin 和 commit 方法一定要相匹配)

Adding Perspective to Your Animations

应用程序可以在三维空间中操作图层,但为了简单起见,Core Animation 使用平行投影显示图层,该投影基本上将场景展平为二维平面。 修改场景的透视图时,需要修改包含正在查看的图层的父层的 sublayerTransform 矩阵。

1
2
3
4
5
6
7
// Listing 5-8  Adding a perspective transform to a parent layer

CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0/eyePosition;

// Apply the transform to a parent layer.
myParentLayer.sublayerTransform = perspective;

eyePosition 值越大,场景越平坦,而值越小,层间的视觉差异越大。 配置父图层后,可以更改任何子图层的 zPosition 属性,并根据它们与眼睛位置的相对距离观察它们的大小如何变化。 (ps: 三维默认是关闭的,要设置父图层的 sublayerTransform 属性后,再设置子图层的 zPosition 才会起作用)

Changing a Layer’s Default Behavior

Core Animation 使用操作对象(action object)为图层实现其隐式动画行为。 操作对象是符合 CAAction 协议的对象,它定义了要在图层上执行的某些相关行为。 所有 CAAnimation 对象都实现了该协议,并且通常会分配这些对象,以便在图层属性发生更改时执行。 (ps: session 2011.421 P26 也有讲到。)

动画属性(animating properties)是一种操作类型,但可以使用我们想要的几乎任何行为来定义操作。但是,要做到这一点,必须定义操作对象并将它们与应用程序的图层对象相关联。

Custom Action Objects Adopt the CAAction Protocol

要创建自己的操作对象,采用 CAAction 协议并实现 runActionForKey:object:arguments: 方法。在该方法中,使用可用信息执行要在图层上执行的任何操作。可以使用该方法将动画对象添加到图层,也可以使用它来执行其他任务。(ps: 动画、图层)

定义操作对象时,必须确定要如何触发该操作。操作的触发器 定义用于稍后注册该操作的 键。可以通过以下任何一种情况触发操作对象:

  • 其中一个图层属性的值已更改。这可以是图层的任何属性,而不仅仅是可动画的属性。 (还可以将操作与添加到图层的自定义属性相关联。)标识此操作的键是属性的名称。
  • 图层变为可见或已添加到图层层次结构中。标识此操作的关键是 kCAOnOrderIn 。
  • 该图层已从图层层次结构中删除。标识此操作的关键是 kCAOnOrderOut 。
  • 该图层即将参与过渡动画。标识此操作的关键是 kCATransition 。

Action Objects Must Be Installed On a Layer to Have an Effect

在可以执行操作之前,该图层需要找到要执行的相应操作对象。 与层相关的操作的关键是要修改的属性的名称或标识操作的特殊字符串。 会调用 actionForKey: 方法来查找,具体请见官方文档。 (ps: session 2011.421 P26 也有讲到。)

如果在任何适当的搜索点提供操作对象,则图层将停止其搜索并执行返回的操作对象。 当它找到一个动作对象时,该层调用该对象的 runActionForKey:object:arguments: 方法来执行该动作。 如果为给定键定义的操作已经是 CAAnimation 类的实例,则可以使用该方法的默认实现来执行动画。 如果要定义符合 CAAction 协议的自定义对象,则必须使用对象的该方法实现来采取适当的操作。

安装操作对象的位置取决于我们打算如何修改图层,具体请见官方文档。(ps: 跟上面的查找方法类似,请注意自定义属性(custom properties)的处理。)

Disable Actions Temporarily Using the CATransaction Class

可以使用 CATransaction 类临时禁用图层操作(layer actions)。 更改图层的属性时, Core Animation 通常会创建一个隐式事务对象来为更改设置动画。 如果不想为更改设置动画,可以通过创建显式事务并将其 kCATransactionDisableActions 属性设置为 true 来禁用隐式动画。 清单6-2显示了一个代码片段,它在从图层树中删除指定图层时禁用动画。

1
2
3
4
5
6
7
// Listing 6-2  Temporarily disabling a layer’s actions

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
forKey:kCATransactionDisableActions];
[aLayer removeFromSuperlayer];
[CATransaction commit];

(ps: session 2011.421 P25 也有讲到 [CATransaction setDisableActions:YES] 禁用隐式动画。)

有关使用事务对象管理动画行为的更多信息,请参阅”#Explicit Transactions Let You Change Animation Parameters#”。

Improving Animation Performance

Core Animation 是提高基于应用程序的动画的帧速率的好方法,但它的使用并不能保证提高性能。 特别是在 OS X 中,仍然必须选择使用 Core Animation 行为的最有效方法。 与所有与性能相关的问题一样,应该使用 Instruments 来衡量和跟踪应用程序的性能,以便确保性能得到改善而不是回归。

Choose the Best Redraw Policy for Your OS X Views

OS X 先不管。

Update Layers in OS X to Optimize Your Rendering Path

OS X 先不管。

General Tips and Tricks

有几种方法可以提高图层实现的效率。 但是,与任何此类优化一样,在尝试优化之前,应始终测量代码的当前性能。 这为提供了一个基线,可用于确定优化是否有效。

Use Opaque Layers Whenever Possible

将图层的 opaque 属性设置为 YES ,可让 Core Animation 知道它不需要为图层维护 alpha 通道。 没有 Alpha 通道意味着合成器(compositor)**不需要将图层的内容与其背景内容混合,这样可以在渲染过程中节省时间。 但是,此属性主要与作为图层支持视图一部分的图层 或 Core Animation 创建基础图层位图的情况相关。 如果将图像直接指定给图层的 contents 属性,则无论** opaque 属性中的值如何,都会保留该图像的 Alpha 通道。

Use Simpler Paths for CAShapeLayer Objects

CAShapeLayer 类通过在复合(composite)时将我们提供的路径渲染到位图图像来创建其内容。优点是该层总是以尽可能好的分辨率绘制路径,但这种优势是以额外的渲染时间为代价的。如果提供的路径很复杂,那么栅格化(rasterizing)该路径可能会过于昂贵。如果图层的大小经常变化(因此必须经常重绘),绘制所花费的时间可能会增加并成为性能瓶颈

优化形状层的绘制时间的一种方法,是将复杂形状分解为更简单的形状。使用更简单的路径并在合成器中将多个 CAShapeLayer 对象叠加在一起可以绘制一个大的复杂路径快得多。这是因为绘图操作发生在 CPU 上,而合成发生在 GPU 上。与此类性质的任何简化一样,潜在的性能提升取决于内容。因此,在优化之前测量代码的性能尤为重要,这样就可以使用基线进行比较。(优化的准则) (ps: 分割就是减少 CPU 的工作量,把工作转移到 GPU 上。)

Set the Layer Contents Explicitly for Identical Layers

如果在多个图层对象中使用相同的图像,请自行加载图像并将其直接指定给这些图层对象的 contents 属性。 将图像分配给 contents 属性可防止图层为后备存储分配内存。 相反,该图层使用我们提供的图像作为其后备存储。 当多个图层使用相同的图像时,这意味着所有这些图层共享相同的内存,而不是为自己分配图像的副本

Always Set a Layer’s Size to Integral Values

为获得最佳效果,请始终将图层对象的宽度和高度设置为整数值。 虽然使用浮点数指定图层边界的宽度和高度,但图层边界(layer bounds)最终用于创建位图图像。 指定宽度和高度的整数值可简化 Core Animation 在创建和管理后备存储和其他图层信息时必须执行的工作。 (ps: bitmap 位图,最终都是加载位图。)

Use Asynchronous Layer Rendering As Needed

在委托的 drawLayer:inContext: 方法或视图的 drawRect: 方法中执行的任何绘图,通常在应用程序的主线程上同步发生。 但在某些情况下,同步绘制内容可能无法提供最佳性能。 如果注意到动画效果不佳,可以尝试在图层上启用 drawAsynchronously 属性,将这些操作移动到后台线程。 如果这样做,请确保绘图代码是线程安全的。 和往常一样,在将其放入生产代码之前,应始终测量异步绘图的性能。

Specify a Shadow Path When Adding a Shadow to Your Layer

让 Core Animation 确定阴影的形状(shape)可能很昂贵并影响应用程序的性能。 不要让 Core Animation 确定阴影的形状,而是使用 CALayer 的 shadowPath 属性显式指定阴影形状。 为此属性指定路径对象时, Core Animation 使用该形状绘制和缓存阴影效果。 对于形状永不变化或很少更改的图层,可以通过减少 Core Animation 完成的渲染量来大大提高性能。

Layer Style Property Animations

在渲染过程中, Core Animation 采用图层的不同属性并按特定顺序呈现它们。 此顺序确定图层的最终外观。 本章说明通过设置不同的图层样式属性实现的渲染结果。

Geometry Properties

几何相关属性。 frame 是从 bounds 和 position 计算而来的,它是不可以动画的。

Background Properties

最开始渲染的背景。 (Q: 是不是可以认为,先背景,然后从下往上渲染,也就是下面内容的顺序)

在 iOS 中的, CALayer.backgroundFilters 设置 filters 是会被忽略的。

Layer Content

所以有内容的话,会在背景上上面渲染内容。 “#Providing a Layer’s Contents#” 中有提到三种设置内容的方式。

Sublayers Content

子图层内容是会循环递归渲染的。

Border Attributes

border 在 content 和 Sublayers Content 的上面。

Filters Property

只有 OS X 才有用。

Shadow Properties

没背景颜色的情况下, Shadow 是添加到图层内容上的。

Opacity Property

不透明度。

Mask Properties

可以使用 Mask 来遮盖图层内容的全部或部分内容。Mask 本身就是一个图层对象,其 alpha 通道用于确定阻塞和传输的内容。Mask 层内容的不透明部分允许底层图像内容透过,而透明部分部分或完全遮盖底层内容。

Animatable Properties

主要是讲了可动画属性的相关默认值。 都是 CABasicAnimation(0.25) 或者 CATransition(0.25) 0.0-1.0 。

Key-Value Coding Extensions

Core Animation 通过 CAAnimation 和 CALayer 扩展了 NSKeyValueCoding 协议。

Key-Value Coding Compliant Container Classes

你可以设置任意的 key ,就算是 CALayer 没有的也行。

1
2
3
4
// 赋值
[theLayer setValue:[NSNumber numberWithInteger:50] forKey:@"someKey"];
// 取值
someKeyValue=[theLayer valueForKey:@"someKey"];

Default Value Support

子类的话,然后重载 defaultValueForKey: 方法。

Wrapping Conventions

结构体或者常量值记得包裹和解包。这里例举了一些属性,记得看看。

Key Path Support for Structures

支持 key path 。注意看这一小节里面的相关 keypath 的含义。

1
2
setValue:forKeyPath:
valueForKeyPath: