版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.09.21 |
前言
app中好的炫的动画可以让用户耳目一新,为产品增色不少,关于动画的实现我们可以用基本动画、关键帧动画、序列帧动画以及基于CoreGraphic的动画等等,接下来这几篇我就介绍下我可以想到的几种动画绘制方法。具体Demo示例已开源到Github —— 刀客传奇,感兴趣的可以看我写的另外几篇。
1. 实现动画方式深度解析(一) —— 播放GIF动画(一)
2. 实现动画方式深度解析(二) —— 播放GIF动画之框架FLAnimatedImage的使用(二)
3. 实现动画方式深度解析(三) —— 播放序列帧动画(一)
4. 实现动画方式深度解析(四) —— QuartzCore框架(一)
5. 实现动画方式深度解析(五) —— QuartzCore框架之CoreAnimation(二)
6. 实现动画方式深度解析(六) —— Core Animation Basics(三)
Setting Up Layer Objects - 设置层layer对象
图层对象是您使用Core Animation
进行的所有操作的核心。 图层管理您的app的可视内容,并提供修改该内容的样式和视觉外观的选项。 尽管iOS app已自动启用图层支持,但OS X app的开发人员必须明确地启用它,才能充分利用性能优势。 启用后,您需要了解如何配置和操作app的图层以获取所需的效果。
Enabling Core Animation Support in Your App - 开启你的App CoreAnimation支持
在iOS应用程序中,始终启用核心动画Core Animation
,每个视图都由图层layer支持。 在OS X中,应用程序必须通过执行以下操作来显式启用Core Animation
支持:
- 链接到
QuartzCore
框架。 (只有在明确使用Core Animation界面的情况下,iOS app才能与此框架链接。) - 通过执行以下操作之一为一个或多个
NSView
对象启用图层支持:- 在您的nib文件中,使用
View Effects
inspector
检查器为您的视图启用图层支持。 检查器将显示所选视图及其子视图的复选框。 建议您尽可能在窗口的内容视图中启用图层支持。 - 对于以编程方式创建的视图,请调用视图的setWantsLayer:方法,并传递值为YES以指示视图应使用图层layer。
- 在您的nib文件中,使用
以上述方式之一启用的层支持创建了层支持的视图。 通过层layer支持的视图,系统负责创建底层图层对象并保持该层更新。 在OS X中,也可以创建一个层托管视图,由此您的app实际创建和管理底层图层对象。 (您不能在iOS中创建层托管视图。)有关如何创建层托管视图的详细信息,请参阅Layer Hosting Lets You Change the Layer Object in OS X。
Changing the Layer Object Associated with a View - 改变与视图对象关联的层对象
默认情况下,层支持的视图创建CALayer
类的实例,并且在大多数情况下,可能不需要不同类型的图层对象。 然而,Core Animation
提供了不同的层类,每个层都提供了您可能会发现有用的专门功能。 选择不同的层类可以使您以简单的方式提高性能或支持特定类型的内容。 例如,CATiledLayer
类被优化以有效的方式显示大图像。
1. Changing the Layer Class Used by UIView - 使用UIView改变layer类
您可以通过重写视图的 layerClass方法并返回不同的类对象来更改iOS视图使用的图层类型。 大多数iOS视图创建一个CALayer对象,并将该层用作其内容的后备存储。 对于大多数你的视图,这个默认选择是一个很好的选择,您不需要更改它。 但是,在某些情况下,您可能会发现不同的层类更合适。 例如,您可能希望在以下情况下更改图层类:
- 你的视图使用Metal或者OpenGL ES进行绘制,在这种情况下,你可以使用CAMetalLayer和CAEAGLLayer。
- 有一个专门的层类提供更好的性能。
- 您想要利用一些专门的核心动画层类,如粒子发射器或复制器。
更改视图的层类非常简单,下面代码中显示了一个示例。 所有你需要做的是重写layerClass
方法并返回要使用的类对象。 在显示之前,视图调用layerClass
方法,并使用返回的类为其自身创建一个新的图层对象。 创建后,视图的图层对象无法更改。
+ (Class) layerClass {
return [CAMetalLayer class];
}
对于layer类有哪些和怎么使用它们,可以参考 Different Layer Classes Provide Specialized Behaviors。
2. Changing the Layer Class Used By NSView - 使用NSView改变layer类
您可以通过重写makeBackingLayer方法来更改NSView
对象使用的默认层类。 在实现此方法时,创建并返回您希望AppKit使用的图层对象来支持自定义视图。 您可以在要使用自定义图层(如滚动或平铺图层)的情况下重写此方法。
对于layer类有哪些和怎么使用它们,可以参考 Different Layer Classes Provide Specialized Behaviors。
3. Layer Hosting Lets You Change the Layer Object in OS X - 层委托允许您更改OS X中的图层对象
层托管视图是一个NSView对象,您可以为其自己创建和管理底层图层对象。 在您想要控制与视图关联的图层对象类型的情况下,可以使用图层托管。 例如,您可以创建一个图层托管视图,以便您可以分配除默认CALayer类之外的图层类。 您也可以在要使用单个视图来管理独立图层的层次结构的情况下使用它。
当您调用视图的setLayer:
方法并提供一个图层对象时,AppKit会对该图层进行切换。 通常,AppKit更新视图的图层对象,但是在图层托管情况下,它不适用于大多数属性。
要创建层托管视图,请在显示屏幕视图之前创建图层对象并将其与视图相关联,如下面代码所示。 除了设置层对象之外,还必须调用setWantsLayer:
方法来让视图知道应该使用图层。
// Creating a layer-hosting view
// Create myView...
[myView setWantsLayer:YES];
CATiledLayer* hostedLayer = [CATiledLayer layer];
[myView setLayer:hostedLayer];
// Add myView to a view hierarchy
如果您自己选择托管图层,则必须自己设置contentsScale
属性,并在适当的时候提供高分辨率的内容。 有关高分辨率内容和比例因子的更多信息,请参阅Working with High-Resolution Images。
4. Different Layer Classes Provide Specialized Behaviors - 不同的层类提供专门的行为
核心动画定义了许多标准层类,每个类都是针对特定用例设计的。 CALayer
类是所有图层对象的根类。 它定义了所有层对象必须支持的行为,并且是由层支持的视图使用的默认类型。 但是,您也可以指定下表中的一个层类。
下表列出来就是CALayer及其子类。
类 | 使用 |
---|---|
CAEmitterLayer | 用于实现基于核心动画的粒子发射器系统。 发射体层对象控制粒子的产生及其起源。 |
CAGradientLayer | 用于绘制填充图层形状的颜色渐变(在任何圆角的边界内)。 |
CAMetalLayer | 用于设置和销售可绘制的纹理,使用Metal 渲染图层内容。 |
CAEAGLLayer/CAOpenGLLayer | 用于设置存储和上下文,以便使用OpenGL ES(iOS)和OpenGL(OS X)进行图层内容渲染。 |
CAReplicatorLayer | 当您想要自动制作一个或多个子图层的副本时使用。 复制器为您提供副本,并使用您指定的属性来更改副本的外观或属性 |
CAScrollLayer | 用于管理由多个子层组成的大型可滚动区域。 |
CAShapeLayer | 用于绘制立方贝塞尔样条。 形状层对于绘制基于路径的形状是有利的,因为它们总是导致清晰的路径,而不是您绘制到图层的后备存储中的路径,而在缩放时它不会看起来好。 然而,清晰的结果确实涉及渲染主线程上的形状并缓存结果。 |
CATextLayer | 用于渲染纯文本或者富文本。 |
CATiledLayer | 用于管理可分为较小的图块和单独渲染的大图像,并支持放大和缩小内容。 |
CATransformLayer | 用于渲染真正的3D层次结构,而不是由其他层类实现的平坦层层次结构 |
QCCompositionLayer | 用于渲染Quartz Composer 构图。 (仅适用于OS X) |
Providing a Layer’s Contents - 提供图层内容
图层是管理由您的应用程序提供的内容的数据对象。 图层的内容由包含要显示的可视数据的位图组成。 您可以通过以下三种方式之一提供该位图的内容:
- 将图像对象直接分配给图层对象的内容属性。 (这种技术最适用于从不或很少改变的图层内容。)
- 将一个代理对象赋值给图层,让代理绘制图层的内容。 (此技术最适合可能会周期性更改并可由外部对象(如视图)提供的图层内容。)
- 定义一个层子类并重写其绘图方法之一,以自己提供图层内容。 (如果您要创建自定义图层子类,或者如果要更改图层的基本绘图行为,则此技术是合适的。)
您唯一需要担心为图层提供内容的时间是您自己创建图层对象时。 如果您的应用程序仅包含层次支持的视图,则不必担心使用上述任何技术来提供图层内容。 层次叠加的视图可以以最有效的方式自动提供其相关层的内容。
1. Using an Image for the Layer’s Content - 使用图像作为图层的内容
因为图层只是用于管理位图图像的容器,因此可以将图像直接分配给图层的内容属性。 为图层分配图像很容易,您可以指定要在屏幕上显示的确切图像。 该图层使用您直接提供的图像对象,并且不会尝试创建该图像的自己的副本。 如果您的应用在多个地方使用相同的图像,这种行为可以节省内存。
分配给图层的图像必须是CGImageRef类型。 (在OS X v10.6及更高版本中,还可以分配NSImage对象。)分配图像时,请记住提供其分辨率与本机设备分辨率相匹配的图像。 对于具有Retina显示器的设备,这可能还需要您调整图像的contentsScale属性。 有关在图层中使用高分辨率内容的信息,请参阅Working with High-Resolution Images。
2. Using a Delegate to Provide the Layer’s Content - 使用代理提供图层的内容
如果你的图层的内容,动态的改变,需要时,你可以使用代理对象提供和更新内容,展示时,图层调用你的代理方法提供需要的内容。
- 如果您代理实现了displayLayer:方法,则该实现负责创建位图并将其分配给图层的内容属性。
- 如果您的委托实现了drawLayer:inContext:方法,Core Animation将创建一个位图,创建一个图形上下文来绘制该位图,然后调用代理方法来填充位图。 所有您的代理方法都必须绘制到提供的图形上下文中。
代理对象必须实现displayLayer:
或drawLayer:inContext:method
。 如果代理同时实现displayLayer:
和drawLayer:inContext:method
,则该层只调用displayLayer:
方法。
重写displayLayer:
方法最适合当您的app想要加载或创建要显示的位图的情况。下面代码显示了displayLayer:
代理方法的示例实现。 在此示例中,代理使用帮助对象来加载和显示所需的映像。 代理方法根据自己的内部状态选择要显示的图像,在该示例中是一个名为displayYesImage
的自定义属性。
//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:
方法动态绘制内容。 下面代码显示了drawLayer:inContext:
方法的示例实现。 在此示例中,代理使用固定宽度和当前渲染颜色绘制简单的曲线路径。
- (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:
方法来绘制您的内容。
在OS X v10.8
及更高版本中,绘图的另一种方法是通过重写您的视图的wantsUpdateLayer
和updateLayer
方法来提供位图。 重写wantUpdateLayer
并返回YES将使NSView类遵循备用渲染路径。 而不是调用drawRect:
,该视图调用您的updateLayer
方法,其实现必须直接将位图分配给图层的内容属性。 这是AppKit希望直接设置视图层对象的内容的场景。
3. Providing Layer Content Through Subclassing - 通过子类提供层内容
如果您正在实现自定义层类,您可以重写图层类的绘图方法来执行任何绘图。 层对象本身生成自定义内容并不常见,但图层肯定可以管理内容的显示。 例如,CATiledLayer
类通过将其分解成可以单独管理和呈现的较小的图块来管理大型图像。 因为只有图层具有关于在任何给定时间需要渲染哪些瓦片的信息,所以它直接管理绘图行为。
当进行子类化时,您可以使用以下任一技术来绘制图层的内容:
重写图层的drawInContext:方法,并使用它在图形上下文中进行绘制。
您重写的方法取决于在绘图过程中需要多少控制。display
方法是更新图层内容的主要入口点,因此重写该方法将使您完全控制该过程。 重写display
方法也意味着您负责创建要分配给contents
属性的CGImageRef
。 如果您只想绘制内容(或让图层管理绘图操作),则可以重写drawInContext:
方法,让图层为您创建后备存储。
4. Tweaking the Content You Provide - 调整您提供的内容
当您将图像分配给图层的内容属性时,图层的contentsGravity
属性可确定如何处理该图像以适应当前边界。 默认情况下,如果图像大于或小于当前边界,则图层对象将缩放图像以适合可用空间。 如果图层的宽高比不同于图像的纵横比,则可能导致图像失真。 您可以使用contentsGravity
属性来确保您的内容以最佳方式呈现。
您可以分配给contentGravity
属性的值分为两类:
- 基于位置的重力常数允许您将图像固定到图层边界矩形的特定边缘或角落,而不缩放图像。
- 基于比例的重力常数允许您使用几个选项之一来拉伸图像,其中一些选项保留纵横比,其中一些选项不保留。
下图显示了基于位置的重力设置如何影响图像。 除了kCAGravityCenter
常数之外,每个常数将图像引导到图层边界矩形的特定边缘或角落。 kCAGravityCenter
常数将层中的图像居中。 这些常量都不会导致以任何方式缩放图像,因此图像始终以其原始大小呈现。 如果图像大于图层的边界,则可能导致图像的部分被剪切,如果图像较小,则图层未被图像覆盖的部分如果设置则显示图层的背景颜色。
下图显示了基于比例的重力常数如何影响图像。 所有这些常数如果不完全符合层的边界矩形,则缩放图像。 模式之间的区别是它们如何处理图像的原始宽高比。 某些模式保留它,而其他模式不保存。 默认情况下,图层的contentGravity
属性设置为kCAGravityResize
常量,这是唯一不保留图像宽高比的模式。
5. Working with High-Resolution Images - 使用高分辨率图像进行处理
层layer对底层设备屏幕的分辨率没有固有的知识。 一个图层简单地存储一个指向你的位图的指针,并以可用像素的最佳方式显示它。 如果将图像分配给图层的内容属性,则必须通过将图层的contentsScale
属性设置为适当的值来告知Core Animation关于图像的分辨率。 属性的默认值为1.0,适用于要在标准分辨率屏幕上显示的图像。 如果您的图像用于Retina显示屏,请将此属性的值设置为2.0。
仅当您直接为图层分配位图时,才需要更改contentsScale
属性的值。 UIKit和AppKit中的层次支持的视图会根据屏幕分辨率和视图管理的内容自动将其图层的比例因子设置为适当的值。 例如,如果您将NSImage
对象分配给OS X中的图层的内容属性,则AppKit将查看是否存在图像的标准和高分辨率变体。 如果有,AppKit将使用正确的变体作为当前分辨率,并设置contentScale
属性的值匹配。
在OS X中,基于位置的重力常数影响从分配给该层的NSImage对象中选择图像表示的方式。 因为这些常量不会导致图像被缩放,Core Animation依靠contentsScale属性来选择最合适的像素密度的图像表示。
在OS X中,图层的委托可以实现图层:shouldInheritContentsScale:fromWindow:
方法,并使用它来响应比例因子的变化。 只要给定窗口的分辨率改变,AppKit就会自动调用该方法,这可能是因为窗口在标准分辨率和高分辨率屏幕之间移动。 如果代理支持更改图层的分辨率,则此方法的实现应返回YES。 该方法应该根据需要更新图层的内容以反映新的分辨率。
Adjusting a Layer’s Visual Style and Appearance - 调整图层的视觉风格和外观
图层对象内置有视觉装饰,如边框和背景颜色,可用于补充图层的主要内容。 因为这些视觉装饰不需要任何渲染,所以它们可以在某些情况下使用层作为独立实体。 所有您需要做的是在图层上设置属性,图层处理必要的图形,包括任何动画。 有关这些视觉装饰如何影响图层外观的其他说明,请参阅Layer Style Property Animations。
1. Layers Have Their Own Background and Border - layer有自己的背景和边界
除了基于图像的内容之外,图层还可以显示填充的背景和绘制边框。 背景颜色呈现在图层的内容图像之后,边框呈现在该图像的顶部,如下图所示。 如果图层包含子图层,它们也会显示在边框的下方。 因为背景颜色位于您的图像后面,那种颜色会透过您图像的任何透明部分,该颜色会透过图像的任何透明部分。
// Setting the background color and border of a layer
myLayer.backgroundColor = [NSColor greenColor].CGColor;
myLayer.borderColor = [NSColor blackColor].CGColor;
myLayer.borderWidth = 3.0;
注意:您可以使用任何类型的颜色作为图层的背景,包括具有透明度或使用图案图像的颜色。 当使用图案图像时,请注意,Core Graphics处理图形图像的渲染,并使用其与iOS中的默认坐标系不同的标准坐标系进行处理。 因此,在iOS上呈现的图像默认显示为颠倒,除非您翻转坐标。
如果将图层的背景颜色设置为不透明的颜色,请考虑将图层的opaque属性设置为YES。 这样做可以在屏幕上合成图层时提高性能,并且不需要层的后备存储来管理Alpha通道。 但是,如果它也具有非零的角半径,则不得将图层标记为不透明。
2. Layers Support a Corner Radius - layer支持圆角
您可以通过向其添加角半径来为图层创建圆角矩形效果。 角半径是一个视觉装饰,可以掩盖图层边界矩形的一部分角落,以允许底层内容显示,如图2-4所示。 因为它涉及应用透明度掩码,所以角度半径不影响图层内容属性中的图像,除非将maskToBounds
属性设置为YES。 但是,角半径始终会影响图层的背景颜色和边框的绘制方式。
要将角半径应用于图层,请为图层的cornerRadius属性指定一个值。 您指定的半径值以点为单位,并在显示之前应用于图层的所有四个角。
3. Layers Support Built-In Shadows - 图层支持内置阴影
CALayer类包括用于配置阴影效果的几个属性。 阴影通过使其看起来像浮动在其底层内容上方,从而增加了图层的深度。 这是另一种类型的视觉装饰,您可能会发现在您的应用的特定情况下有用。 使用图层,您可以控制阴影的颜色,相对于图层的内容,不透明度和形状的位置。
默认情况下,层阴影的不透明度值设置为0,这有效地隐藏阴影。 将不透明度更改为非零值将导致Core Animation绘制阴影。 因为默认情况下,阴影直接位于图层之下,您可能还需要更改阴影的偏移量才能看到它。 重要的是要记住,您为阴影指定的偏移量是使用层的本机坐标系进行应用的,这在iOS和OS X上是不同的。下图显示了一个阴影层向下延伸到 层的右边 在iOS中,这需要为y轴指定正值,但在OS X中,该值需要为负值。
当向图层添加阴影时,阴影是图层内容的一部分,但实际上会延伸到图层的边界矩形之外。 因此,如果您为图层启用masksToBounds
属性,阴影效果将围绕边缘进行裁剪。 如果您的图层包含任何透明的内容,这可能会导致奇怪的影响,直接在图层下方的阴影部分仍然可见,但超出图层的部分不是。 如果你想要一个阴影,但也想使用边界掩蔽,你使用两层而不是一层。 将蒙版mask应用于包含内容的图层,然后将该图层嵌入到具有启用阴影效果的完全相同大小的第二层中。
对于在layer中如何应用阴影,可以参考 Shadow Properties。
4. Filters Add Visual Effects to OS X Views
在OS X应用程序中,您可以将Core Image过滤器直接应用到图层的内容。 您可以这样做来模糊或锐化图层的内容,更改颜色,扭曲内容或执行许多其他类型的操作。 例如,图像处理程序可以使用这些滤波器非破坏性地修改图像,而视频编辑程序可能使用它们来实现不同类型的视频转换效果。 并且因为过滤器以硬件应用于图层的内容,渲染速度快,流畅。
注意:在iOS中,你不能给layer对象添加滤波器。
对于给定的层,您可以将过滤器应用于图层的前景和背景内容。 前景内容由图层本身包含的所有内容组成,包括其内容属性中的图像,其背景颜色,边框及其子图层的内容。 背景内容是直接在图层下的内容,但实际上不是图层本身的一部分。 大多数层的背景内容是其直接上层的内容,其可以被层完全或部分地遮蔽。 例如,当您希望用户专注于图层的前景内容时,您可以对背景内容应用模糊滤镜。
您可以通过将CIFilter对象添加到图层的以下属性来指定过滤器:
- filters属性包含仅影响图层前台内容的过滤器数组。
- backgroundFilters属性包含了影响layer背景内容的一组滤波器。
- compositingFilter属性定义了 foreground 和 background内容是如何合成在一起的。
要向图层添加过滤器,您必须先找到并创建CIFilter
对象,然后在将其添加到图层之前对其进行配置。 CIFilter类包括几个类方法来定位可用的Core Image
过滤器,例如filterWithName:
方法。 创建过滤器只是第一步。 许多滤镜具有定义滤镜如何修改图像的参数。 例如,框模糊滤镜具有影响所应用模糊量的输入半径参数。 您应该始终为这些参数提供值作为过滤器配置过程的一部分。 但是,您不需要指定的一个常用参数是输入图像,它是由图层本身提供的。
向图层添加过滤器时,最好在将过滤器添加到图层之前配置过滤器参数。 这样做的主要原因是一旦添加到图层,就不能修改CIFilter对象本身。 但是,您可以使用图层的setValue:forKeyPath:
方法在事后更改过滤器值。
下面显示了如何为层对象创建和应用捏合失真过滤器。 该过滤器向内夹持层的源像素,使最接近指定中心点的那些像素失真。 请注意,在示例中,您不需要为滤镜指定输入图像,因为图层的图像会自动使用。
//Applying a filter to a layer
CIFilter* aFilter = [CIFilter filterWithName:@"CIPinchDistortion"];
[aFilter setValue:[NSNumber numberWithFloat:500.0] forKey:@"inputRadius"];
[aFilter setValue:[NSNumber numberWithFloat:1.25] forKey:@"inputScale"];
[aFilter setValue:[CIVector vectorWithX:250.0 Y:150.0] forKey:@"inputCenter"];
myLayer.filters = [NSArray arrayWithObject:aFilter];
对于更多关于 Core Image filters
,可以参考 Core Image Filter Reference。
The Layer Redraw Policy for OS X Views Affects Performance - OS X视图的图层重绘策略会影响性能
在OS X中,层次支持的视图支持几种不同的策略来确定何时更新底层的内容。 因为本地AppKit绘图模型与Core Animation引入的绘图模型之间存在差异,所以这些策略可以将较旧的代码迁移到Core Animation中更容易。 您可以在逐个视图的基础上配置这些策略,以确保每个视图的最佳性能。
Adding Custom Properties to a Layer - 图层添加自定义属性
CAAnimation
和CALayer
类扩展了键值编码约定以支持自定义属性。 您可以使用此行为将数据添加到图层,并使用您定义的自定义键检索它。 您甚至可以将操作与自定义属性相关联,以便在更改属性时,将执行相应的动画。
有关如何设置和获取自定义属性的信息,请参阅Key-Value Coding Compliant Container Classes。 有关向层对象添加操作的信息,请参阅Changing a Layer’s Default Behavior。
Printing the Contents of a Layer-Backed View - 打印层支持的视图内容
在打印过程中,图层根据需要重绘其内容以适应打印环境。 而核心动画通常在渲染到屏幕时依赖于缓存的位图,当打印时它会重绘该内容。 特别是,如果层支持的视图使用drawRect:
方法来提供图层内容,Core Animation会在打印期间再次调用drawRect:
生成打印图层内容。
后记
未完,待续~~