The Relationship Between Layers and Views
每个视图都有相应的层对象支持。视图只是层对象的一个封装。
层管理您的应用程序的视觉内容,并提供选项来修改内容的风格和视觉外观。
坐标系分为点坐标系和单位坐标系。都是float。
锚点和旋转。
使用Core Animation的应用程序有三组图层对象。每一组层对象在使你的应用程序的内容出现在屏幕上有不同的作用:
1.模型层树(或简单的“层树”)中的对象是应用程序与之交互最多的对象。这个树中的对象是模型对象,它存储任何动画的目标值。每当您更改一个层的属性时,您就会使用这些对象中的一个。
2.呈现树中的对象包含任何正在运行的动画的正在运行的值。层树对象包含动画的目标值,而表示树中的对象在屏幕上显示时反映当前的值。永远不要修改树中的对象。相反,您可以使用这些对象来读取当前动画值,或者从这些值开始创建一个新的动画。只有当动画在运行时,才应该访问表示树中的对象。当动画正在进行时,表示树包含了当时出现在屏幕上的层值。这种行为与层树不同,层树总是反映代码设置的最后一个值,相当于动画的最终状态。
3.渲染树中的对象执行实际的动画,并且是私有的。
改变UIView使用的层类可以通过覆盖视图的layerClass方法并返回不同的类对象来更改iOS视图使用的层类型。比如一下情况:
您的视图使用Metal或OpenGL ES绘制内容,在这种情况下,您将使用CAMetalLayer或CAEAGLLayer对象。
有一个专门的层类提供更好的性能。
你想利用一些专门的核心动画层类,如粒子发射器或复制器。
您所要做的就是重写layerClass方法并返回您想要使用的类对象。在显示之前,视图调用layerClass方法并使用返回的类为自己创建一个新的层对象。一旦创建,视图的层对象就无法更改。
+ (Class) layerClass {
return [CAMetalLayer class];
}
层是管理应用程序提供的内容的数据对象。层的内容由一个位图组成,其中包含要显示的可视数据。您可以通过以下三种方式之一提供该位图的内容:
将图像对象直接分配给层对象的内容属性。(这种技术最适用于从未或很少发生变化的层内容。)
给层分配一个委托对象,让委托绘制层的内容。(这种技术最适用于可能周期性变化的、可以由外部对象(比如视图)提供的层内容。)
定义一个层子类并覆盖它的一个绘图方法来提供层内容。(如果无论如何你都要创建一个自定义层子类,或者你想改变层的基本绘制行为,这种技术是合适的。)
唯一需要担心为层提供内容的时间是您自己创建层对象时。如果您的应用程序只包含层支持的视图,那么您不必担心使用前面的任何技术来提供层内容。支持层的视图自动以最有效的方式为其关联层提供内容。
因为层只是管理位图图像的容器,所以您可以直接将图像分配给层的内容属性。为层分配图像很容易,并且允许您指定要在屏幕上显示的确切图像。该层直接使用您提供的图像对象,而不尝试创建该图像的自身副本。如果应用程序在多个位置使用相同的图像,这种行为可以节省内存。分配给图层的图像必须是CGImageRef类型。
[self.view.layer setContents:[UIImage imageNamed:@"Image"].CGImage];
如果你图层的内容是动态变化的,你可以使用委托对象在需要的时候提供和更新内容。在显示时,层调用委托的方法来提供所需的内容:
1.如果您的委托实现了displayLayer:方法,该实现负责创建位图并将其分配给层的contents属性。
2.如果你的委托实现了drawLayer:inContext:方法,Core Animation会创建一个位图,创建一个图形上下文来绘制那个位图,然后调用你的委托方法来填充这个位图。您的委托方法所要做的就是绘制到提供的图形上下文中。
委托对象必须实现displayLayer:或drawLayer:inContext:方法。如果委托实现了displayLayer:和drawLayer:inContext:方法,那么该层只调用displayLayer:方法。
- (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];
}
}
- (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:方法来绘制内容。
当子类化时,你可以使用以下任何一种技术来绘制你的层的内容:
1.重写层的display方法,并使用它直接设置层的内容属性。
2.重写层的drawInContext: 方法并使用它来绘制到提供的图形上下文。
重写哪个方法取决于对绘图过程需要多少控制。display方法是更新层内容的主要入口点,因此重写该方法将使您完全控制流程。重写display方法还意味着您负责创建要分配给contents属性的CGImageRef。如果您只想绘制内容(或者让您的层管理绘图操作),您可以重写drawInContext:方法,并让层为您创建备份存储。
当您将图像分配给一个层的contents属性时,该层的contentsGravity属性决定了如何操作该图像以适应当前边界。默认情况下,如果图像大于或小于当前边界,层对象将缩放图像以适应可用空间。如果层边界的长宽比与图像的长宽比不同,就会导致图像失真。您可以使用contentsGravity属性来确保您的内容以最好的方式呈现。
可以将contentsGravity属性赋值分为两类:
1.基于位置的常量允许您在不缩放图像的情况下将图像固定到图层边界矩形的特定边缘或角落。
2.基于缩放的常量允许您使用几种选项中的一种来拉伸图像,其中一些选项可以保持高宽比,而另一些则不能。
Options are center',
top', bottom',
left', right',
topLeft', topRight',
bottomLeft', bottomRight',
resize'//原比例, resizeAspect'//按照layer大小缩放,
resizeAspectFill'//按比例充满layer,bounds可能比layer的bounds大.
处理高分辨率图像
层没有任何关于底层设备屏幕分辨率的固有知识。层只是存储一个指向位图的指针,并在给定可用像素的情况下以最好的方式显示它。如果您将图像分配给一个图层的内容属性,您必须通过将图层的contentsScale属性设置为适当的值来告诉Core Animation关于图像的分辨率。属性的默认值是1.0,这对于打算在标准分辨率屏幕上显示的图像是合适的。如果您的图像是用于视网膜显示的,请将此属性的值设置为2.0。
只有在直接将位图分配给层时,才需要更改contentsScale属性的值。UIKit和AppKit中支持层的视图根据屏幕分辨率和视图管理的内容自动将其层的比例因子设置为适当的值。
定义应用于层内容的比例因子。如果内容的物理大小是'(w, h)',那么逻辑大小(即contentsGravity计算)定义为'(w / contentsScale, h / contentsScale)'。应用于显式提供的图像和通过-drawInContext提供的内容:(例如,如果contentsScale是2-drawInContext:将绘制到比层边界大两倍的缓冲区)。默认为1。可以做成动画。
图层对象内建了一些视觉装饰,比如边框和背景颜色,你可以用它们来补充图层的主要内容。因为这些视觉装饰不需要你的任何渲染,它们使得在某些情况下使用层作为独立实体成为可能。你所要做的就是在图层上设置一个属性,图层处理必要的绘图,包括任何动画。
除了基于图像的内容之外,层还可以显示填充的背景和描边。背景颜色呈现在图层内容图像的后面,边框呈现在图像的顶部,如图2-3所示。如果图层包含子图层,它们也会出现在边框下面。因为背景颜色位于图像的后面,所以这种颜色可以穿透图像的任何透明部分。
myLayer.backgroundColor = [NSColor greenColor].CGColor;
myLayer.borderColor = [NSColor blackColor].CGColor;
myLayer.borderWidth = 3.0;
您可以使用任何类型的颜色作为图层的背景,包括具有透明度或使用图案图像的颜色。不过,在使用模式图像时,请注意,核心图形处理模式图像的呈现,并使用其标准坐标系统进行呈现,这与iOS中的默认坐标系统不同。因此,除非你翻转坐标,否则在iOS上呈现的图像默认情况下是颠倒的。
如果你将图层的背景颜色设置为不透明颜色,考虑将图层的不透明属性设置为YES。这样做可以在屏幕上合成层时提高性能,并消除层的备份存储管理alpha通道的需要。但是,如果一个图层的转角半径不为零,就不能将其标记为不透明。
你可以为你的图层添加一个圆角半径来创建一个圆角矩形效果。角半径是一种视觉装饰,它覆盖了层边界矩形的部分角,允许底层内容通过。因为它涉及到应用一个透明遮罩,除非masksToBounds属性设置为YES,否则角半径不会影响图层内容属性中的图像。然而,转角半径总是会影响图层的背景颜色和边框的绘制。
要将转角半径应用于层,请为层的转角半径属性指定一个值。指定的半径值以点为单位测量,并应用于显示层的所有四个角。
CALayer类包含了一些配置阴影效果的属性。阴影通过使它看起来像漂浮在底层内容之上,从而增加了层的深度。这是另一种视觉装饰,在应用程序的特定情况下,你可能会发现它很有用。有了图层,你可以控制阴影的颜色、相对于图层内容的位置、不透明度和形状。
图层阴影的不透明度值默认设置为0,这有效地隐藏了阴影。将不透明度更改为非零值会导致Core Animation绘制阴影。因为在默认情况下,阴影是直接位于图层下面的,所以在你看到它之前,你可能还需要改变阴影的偏移量。不过,要记住,您为阴影指定的偏移量是使用层的本机坐标系统应用的。
当向图层添加阴影时,阴影是图层内容的一部分,但实际上扩展到了图层边界矩形之外。因此,如果你为图层启用了masksToBounds属性,阴影效果就会被边缘剪掉。如果你的图层包含了任何透明的内容,这可能会导致一个奇怪的效果,那就是你的图层下面的阴影部分仍然可见,而你的图层之外的部分则不可见。如果你想要一个阴影但又想要使用边界掩蔽,你使用两个层而不是一个。将蒙版应用到包含内容的层中,然后将该层嵌入到启用阴影效果的大小完全相同的第二层中。
要触发隐式动画,只需更新图层对象的属性即可。当修改层树中的层对象时,这些对象会立即反映出您的更改。然而,层对象的视觉外观不会立即改变。取而代之的是,Core Animation使用你的修改作为触发器来创建和调度一个或多个隐式动画来执行。因此,进行如清单3-1所示的更改会导致Core Animation为您创建一个动画对象,并安排动画在下一次更新周期开始运行。
theLayer.opacity = 0.0;
要显式地使用动画对象进行相同的更改,请创建一个CABasicAnimation对象并使用该对象配置动画参数。您可以设置动画的开始和结束值,更改持续时间,或在将动画添加到一个层之前更改任何其他动画参数。清单3-2展示了如何使用动画对象淡出图层。在创建对象时,为要动画的属性指定关键路径,然后设置动画参数。要执行动画,可以使用addAnimation:forKey:方法将其添加到想要动画的层中。
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;
与隐式动画(更新层对象的数据值)不同,显式动画不修改层树中的数据。显式动画只产生动画。在动画结束时,Core animation从图层中移除动画对象,并使用当前的数据值重新绘制图层。如果您想让显式动画的更改成为永久性的,您还必须更新层的属性,如前面的示例所示。
隐式和显式动画通常在当前运行循环周期结束后开始执行,为了执行动画,当前线程必须有一个运行循环。如果您更改多个属性,或者向一个层添加多个动画对象,那么所有这些属性更改都将同时进行动画。例如,通过同时配置两个动画,可以在屏幕外移动图层的同时淡出。不过,您还可以配置动画对象,使其在特定时间启动。
基于属性的动画将属性从开始值更改为结束值,而CAKeyframeAnimation对象允许以可能是或可能不是线性的方式通过一组目标值进行动画。关键帧动画由一组目标数据值和达到每个值的时间组成。在最简单的配置中,使用数组指定值和时间。对于更改层的位置,您还可以让更改遵循一条路径。animation对象获取您指定的关键帧,并通过在给定的时间段内从一个值插值到下一个值来构建动画。
// 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);
.
// Add the animation to the layer.
[theLayer addAnimation:theAnimation forKey:@"position"];
这里面的坐标是锚点坐标。
关键帧值是关键帧动画中最重要的部分。这些值定义了动画在执行过程中的行为。指定关键帧值的主要方法是作为对象数组,但是对于包含CGPoint数据类型(例如层的锚点和位置属性)的值,可以指定CGPathRef数据类型。
在指定值数组时,放入数组的内容取决于属性所需的数据类型。可以直接向数组中添加一些对象;但是,在添加某些对象之前,必须将其转换为id,所有标量类型或结构都必须由对象包装。例如:
1.对于接受CGRect的属性(如界限和框架属性),将每个矩形包装在NSValue对象中。
2.对于层的transform属性,将每个CATransform3D矩阵包装在一个NSValue对象中。此属性的动画化将导致关键帧动画依次将每个转换矩阵应用到层。
3.对于borderColor属性,在将每个CGColorRef数据类型添加到数组之前,将其转换为类型id。
4.对于接受CGFloat值的属性,在将每个值添加到数组之前将其包装在NSNumber对象中。
5.当动画层的内容属性时,指定一个CGImageRef数据类型数组。
对于采用CGPoint数据类型的属性,您可以创建一个点数组(包装在NSValue对象中),也可以使用CGPathRef对象指定要遵循的路径。当指定一个点数组时,关键帧动画对象在每个连续的点之间画一条直线,并沿着这条路径。当您指定一个CGPathRef对象时,动画从路径的起始点开始,沿着它的轮廓,包括沿着任何曲面展开。您可以使用开路径或闭路径。
关键帧动画的时间和节奏比基本动画更复杂,你可以使用几个属性来控制它:
1.calculationMode属性定义了用于计算动画计时的算法。这个属性的值会影响其他与时间相关的属性的使用。
1-1.线性和立方动画—即,将calculationMode属性设置为kCAAnimationLinear或kcaanimationcube的动画—使用提供的时间信息生成动画。这些模式可以最大限度地控制动画的时间。
1-2.定步动画——即将calculationMode属性设置为kcaanimationpace或kcaanimation立方的动画——不依赖于keyTimes或timingFunctions属性提供的外部计时值。相反,定时值是隐式计算的,以提供恒定速度的动画。
1-3.离散动画——也就是说,将calculationMode属性设置为kcaanimationdiscrete的动画——会导致动画属性从一个关键帧值跳转到下一个关键帧值,而不需要任何插值。这种计算模式使用keyTimes属性中的值,但忽略timingFunctions属性
2.keyTimes属性指定应用每个关键帧值的时间标记。此属性仅在计算模式设置为kCAAnimationLinear、kCAAnimationDiscrete或kCAAnimationCubic时才使用。它不用于有节奏的动画。
3.timingFunctions属性指定每个关键帧段使用的时间曲线。(此属性替换继承的timingFunction属性。)
如果您想自己处理动画计时,请使用kCAAnimationLinear或kCAAnimationCubic模式以及keyTimes和timingFunctions属性。关键时间定义了应用每个关键帧值的时间点。所有中间值的时序由时序函数控制,它允许您对每个段应用易入曲线或易出曲线。如果不指定任何计时函数,计时是线性的。
动画通常运行到完成,但如果需要,你可以使用以下技术之一提前停止:
1.要从层中删除单个动画对象,请调用层的removeAnimationForKey:方法来删除动画对象。这个方法使用传递给addAnimation:forKey:方法的键来识别动画。指定的键不能为nil。
2.要从层中删除所有动画对象,请调用层的removeallanimation方法。此方法立即删除所有正在进行的动画,并使用其当前状态信息重新绘制该层。
当你从一个图层中移除一个动画时,Core animation会使用当前的值重新绘制这个图层。因为当前值通常是动画的结束值,这可能会导致层的外观突然跳转。如果你想让图层的外观保持在动画的最后一帧的位置,你可以使用表示树中的对象来检索这些最终值,并将它们设置在图层树中的对象上。
如果您想同时对一个层对象应用多个动画,您可以使用CAAnimationGroup对象将它们组合在一起。通过提供一个配置点,使用group对象可以简化对多个动画对象的管理。应用于组的时间和持续时间值会覆盖单个动画对象中的相同值
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"];
将动画组织在一起的更高级的方法是使用事务对象。事务通过允许您创建嵌套的动画集并为每个动画分配不同的参数,从而提供了更大的灵活性
核心动画提供了检测动画何时开始或结束的支持。这些通知是做任何与动画相关的家务工作的好时机。例如,您可以使用开始通知来设置一些相关的状态信息,并使用相应的结束通知来删除该状态。
关于动画状态有两种不同的通知方式:
1.使用setCompletionBlock:方法向当前事务添加一个完成块。当事务完成时,事务执行完成块。
2.为CAAnimation对象分配一个委托,并实现animationDidStart:和animationDidStop:finished: delegate方法。
如果您希望将两个动画链接在一起,以便其中一个动画在另一个动画完成时启动,那么不要使用动画通知。相反,使用动画对象的beginTime属性在需要的时间启动每个对象。将两个动画链接在一起,将第二个动画的开始时间设置为第一个动画的结束时间。
如何动画支持层视图
因为iOS视图总是有一个底层,UIView类本身直接从层对象中获取大部分数据。因此,您对该层所做的更改也会被视图对象自动反射。这个行为意味着你可以使用Core Animation或者UIView接口来做你的改变。
如果您想使用Core Animation类来初始化动画,您必须在基于视图的动画块中发出所有的Core Animation调用。UIView类默认情况下禁用层动画,但在动画块中重新启用它们。因此,在动画块之外所做的任何更改都不是动画的。清单3-5显示了如何隐式地更改图层的不透明度及其位置的示例。在本例中,myNewPosition变量是预先计算的,并由块捕获。这两个动画同时启动,但是不透明度动画以默认的时间运行,而位置动画以其动画对象中指定的时间运行。
[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"];
}];
如果您正在使用基于约束的布局规则来管理视图的位置,则必须在配置动画时删除任何可能干扰动画的约束。约束会影响您对视图的位置或大小所做的任何更改。它们还影响视图及其子视图之间的关系。如果要对这些项中的任何项进行动画化更改,可以删除约束,进行更改,然后应用所需的任何新约束。
某些父层属性会影响应用到其子层的任何动画的行为。其中一个属性是speed属性,它是动画速度的乘数。默认情况下,该属性的值设置为1.0,但将其更改为2.0会使动画以两倍于初始速度运行,从而在一半时间内完成。这个属性不仅影响设置它的层,也影响该层的子层。这样的变化也是多种多样的。如果一个子层和它的超层的速度都是2.0,那么子层上的动画运行速度是原来速度的4倍。
Core Animation支持几个选项来调整子图层的大小和位置,以响应对超层的改变。在iOS中,对层支持视图的广泛使用使得层层次结构的创建不那么重要;只支持手动布局更新。
只有在使用创建的独立层对象构建层层次结构时,层级别布局才相关。如果应用程序的层都与视图相关,那么使用基于视图的布局支持来更新视图的大小和位置,以响应更改。
手动布局您的层层次结构
在iOS和OS X上,您可以通过在超级层的委托对象上实现layoutSublayersOfLayer:方法来手动处理布局。您可以使用该方法来调整当前嵌入到层中的任何子文件的大小和位置。在进行手动布局更新时,您需要执行必要的计算来定位每个子层。
如果要实现自定义层子类,则子类可以覆盖layoutSublayers方法,并使用该方法(而不是委托)来处理任何布局任务。只有在需要完全控制自定义层类中子层的位置时,才应该重写此方法。
子层和剪裁
与视图不同,超层不会自动剪切位于其边界矩形外的子层的内容。相反,在默认情况下,超层允许其子文件完整显示。但是,您可以通过将图层的masksToBounds属性设置为YES来重新启用裁剪。
层剪切掩模的形状包括层的角半径,如果指定了角半径的话。显示一个层,该层演示了masksToBounds属性如何影响圆角层。当属性设置为NO时,子文件将完整显示,即使它们超出了父层的界限。将属性更改为YES会导致内容被剪切。
有时,您可能需要将一层中的坐标值转换为另一层中相同屏幕位置的坐标值。CALayer类提供了一组简单的转换例程,您可以使用它们来实现这个目的:
convertPoint:fromLayer:
convertPoint:toLayer:
convertRect:fromLayer:
convertRect:toLayer:
除了转换点和矩形值之外,您还可以使用convertTime:fromLayer:和convertTime:toLayer:方法在层之间转换时间值。每个层定义自己的本地时间空间,并使用该时间空间与系统的其他部分同步动画的开始和结束。默认情况下,这些时间空间是同步的;然而,如果你改变了一组图层的动画速度,那么这些图层的时间空间就会相应改变。您可以使用时间转换方法来考虑任何此类因素,并确保两个层的时间同步。
计时是动画的重要组成部分,在核心动画中,您可以通过CAMediaTiming协议的方法和属性为动画指定精确的计时信息。两个核心动画类采用了这个协议。CAAnimation类采用了它,这样您就可以在动画对象中指定时间信息。CALayer也采用了它,这样您就可以为您的隐式动画配置一些与时间相关的特性,尽管封装这些动画的隐式事务对象通常提供优先的默认时间信息。
当考虑时间和动画的时候,理解层对象如何与时间一起工作是很重要的。每一层都有自己的本地时间,用来管理动画计时。通常,两个不同层的本地时间足够接近,您可以为每个层指定相同的时间值,而用户可能不会注意到任何东西。然而,一个层的本地时间可以被它的父层或它自己的计时参数修改。例如,改变图层的速度属性会导致该图层(及其子图层)的动画持续时间成比例地改变。
为了帮助您确保时间值适合于给定的层,CALayer类定义了convertTime:fromLayer:和convertTime:toLayer:方法。您可以使用这些方法将固定的时间值转换为层的本地时间或将时间值从一层转换为另一层。这些方法考虑到可能影响层的本地时间的媒体定时属性,并返回一个可以与另一层一起使用的值。CACurrentMediaTime函数是一个方便的函数,它返回计算机的当前时钟时间,该方法接受并转换为层的本地时间。
CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];
一旦在层的本地时间中有了时间值,就可以使用该值更新动画对象或层的与时间相关的属性。有了这些定时属性,你可以实现一些有趣的动画行为,包括:
1.使用beginTime属性设置动画的开始时间。通常,动画会在下一次更新周期中开始。您可以使用beginTime参数将动画启动时间延迟几秒钟。将两个动画链接在一起的方法是设置一个动画的开始时间来匹配另一个动画的结束时间。
如果延迟动画的开始,您可能还希望将fillMode属性设置为kCAFillModeBackwards。这个填充模式会导致图层显示动画的起始值,即使图层树中的图层对象包含不同的值。如果没有这个填充模式,您将看到在动画开始执行之前跳转到最终值。其他填充模式也可用。
2.autoreverses属性使动画在指定的持续时间内执行,然后返回到动画的起始值。您可以将此属性与repeatCount属性组合起来,在开始值和结束值之间来回动画化。为autoreversing动画将重复计数设置为整数(例如1.0),会导致动画停止在其初始值上。添加额外的半步(例如重复计数为1.5)会导致动画在其结束值上停止。
3.在组动画中使用timeOffset属性可以在较晚的时间启动一些动画。
要暂停动画,您可以利用图层采用CAMediaTiming协议的事实,并将图层动画的速度设置为0.0。将速度设置为0会暂停动画,直到将值更改为非零值
-(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;
}
对一个层所做的每一次更改都必须是事务的一部分。CATransaction类管理动画的创建和分组以及在适当的时间执行。在大多数情况下,您不需要创建自己的事务。当您向某个层添加显式或隐式动画时,Core Animation自动创建一个隐式事务。不过,您还可以创建显式事务来更精确地管理这些动画。
使用CATransaction类的方法创建和管理事务。要启动(并隐式创建)一个新的事务调用begin类方法;要结束该事务,请调用commit类方法。在这些调用之间是您希望成为事务一部分的更改。
[CATransaction begin];
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];
使用事务的主要原因之一是,在显式事务的范围内,您可以更改持续时间、计时函数和其他参数。您还可以为整个事务分配一个完成块,以便在动画组完成时通知应用程序。更改动画参数需要使用setValue:forKey:方法修改事务字典中的适当键。例如,要将默认持续时间更改为10秒,需要更改kCATransactionAnimationDuration键
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
forKey:kCATransactionAnimationDuration];
// Perform the animations
[CATransaction commit];
您可以在希望为不同的动画集提供不同的默认值的情况下嵌套事务。要将一个事务嵌套到另一个事务中,只需再次调用begin类方法。每个begin调用必须匹配到提交方法的相应调用。只有在提交最外层事务的更改后,Core Animation才会启动相关的动画。
[CATransaction begin]; // Outer transaction
// Change the animation duration to two seconds
[CATransaction setValue:[NSNumber numberWithFloat:2.0f]
forKey:kCATransactionAnimationDuration];
// Move the layer to a new position
theLayer.position = CGPointMake(0.0,0.0);
[CATransaction begin]; // Inner transaction
// Change the animation duration to five seconds
[CATransaction setValue:[NSNumber numberWithFloat:5.0f]
forKey:kCATransactionAnimationDuration];
// Change the zPosition and opacity
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];
[CATransaction commit];
应用程序可以在三维空间中操作图层,但为了简单起见,Core Animation使用平行投影显示图层,将场景压平成二维平面。这种默认行为导致具有不同zPosition值的大小相同的层显示为相同的大小,即使它们在z轴上相距很远。你通常在三维空间中看到这样一个场景的视角已经消失了。然而,您可以通过修改层的转换矩阵来包括透视图信息来改变这种行为。
当修改场景的透视图时,您需要修改包含要查看的层的超层的subblayertransform矩阵。通过对所有子层应用相同的透视图信息,修改超层可以简化您必须编写的代码。它还确保正确地将透视图应用于在不同平面上相互重叠的兄弟子文件。
显示了为父层创建简单透视图转换的方法。在本例中,定制的眼睛位置变量指定了沿z轴的相对距离,从z轴可以查看各个层。通常你指定一个正值的目视,以保持层的方向在预期的方式。更大的值会导致更平坦的场景,而更小的值会导致层间更明显的视觉差异
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0/eyePosition;
// Apply the transform to a parent layer.
myParentLayer.sublayerTransform = perspective;
通过配置父层,您可以更改任何子层的zPosition属性,并根据它们与眼睛位置的相对距离观察它们的大小如何变化。
核心动画实现了它的隐式动画行为层使用动作对象。action对象是一个符合CAAction协议的对象,并且定义了一些相关的行为在一个层上执行。所有CAAnimation对象都实现了协议,这些对象通常在层属性改变时被分配执行。
动画属性是一种操作类型,但是您几乎可以用任何您想要的行为来定义操作。不过,要做到这一点,你必须定义你的动作对象,并将它们与应用程序的层对象相关联。
自定义操作对象采用CAAction协议
要创建自己的动作对象,从类中采用CAAction协议并实现runActionForKey:object:arguments:方法。在该方法中,使用可用信息在层上执行您想要执行的任何操作。您可以使用该方法向层添加一个动画对象,也可以使用它执行其他任务。
在定义动作对象时,必须决定如何触发该动作。动作的触发器定义了稍后用来注册该动作的键。以下任何一种情况都可能触发动作对象:
1.其中一个层属性的值改变了。这可以是层的任何属性,而不仅仅是可动画的属性。(您还可以将操作与添加到层中的自定义属性相关联。)标识此操作的键是属性的名称。
2.该层变得可见或被添加到层层次结构。识别此操作的键是kCAOnOrderIn。
3.该层从层层次结构中删除。标识此操作的键是kCAOnOrderOut。
4.该层即将涉及到一个过渡动画。识别这个动作的关键是位置。
在执行操作之前,层需要找到要执行的相应操作对象。与层相关的操作的键要么是正在修改的属性的名称,要么是标识操作的特殊字符串。当层上发生适当的事件时,该层调用它的actionForKey:方法来搜索与该键相关联的操作对象。您的应用程序可以在搜索过程中在几个点插入自己,并为该键提供相关的操作对象。
核心动画寻找以下顺序的动作对象:
如果该层有一个委托,而该委托实现actionForLayer:forKey:方法,则该层调用该方法。委托必须执行以下操作之一:
返回给定键的操作对象。
如果不处理该操作,返回nil,在这种情况下,搜索将继续。
返回NSNull对象,在这种情况下搜索立即结束。
层在层的动作字典中查找给定的键。
该层在样式字典中查找包含键的动作字典。(换句话说,样式字典包含一个动作键,其值也是一个字典。层在第二个字典中查找给定的键。
层调用它的defaultActionForKey:类方法。
该层执行核心动画定义的隐式动作(如果有的话)。
如果在任何适当的搜索点提供一个操作对象,该层将停止搜索并执行返回的操作对象。当它找到一个动作对象时,层调用该对象的runActionForKey:object:arguments:方法来执行动作。如果为给定键定义的操作已经是CAAnimation类的实例,则可以使用该方法的默认实现来执行动画。如果您正在定义符合CAAction协议的自定义对象,则必须使用该方法的对象实现来采取任何适当的操作。
在何处安装操作对象取决于您打算如何修改该层。
对于可能只在特定情况下应用的操作,或者对于已经使用委托对象的层,提供委托并实现其actionForLayer:forKey:方法。
对于通常不使用委托的层对象,将操作添加到层的操作字典中。
对于在层对象上定义的与自定义属性相关的操作,请在层样式字典中包含该操作。
对于对层的行为至关重要的操作,子类化层并覆盖defaultActionForKey:方法。
forKey:(NSString *)theKey {
CATransition *theAnimation=nil;
if ([theKey isEqualToString:@"contents"]) {
theAnimation = [[CATransition alloc] init];
theAnimation.duration = 1.0;
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
theAnimation.type = kCATransitionPush;
theAnimation.subtype = kCATransitionFromRight;
}
return theAnimation;
}
可以使用CATransaction类临时禁用层操作。当您更改一个层的属性时,Core Animation通常会创建一个隐式的事务对象来动画更改。如果您不想将更改动画化,可以通过创建显式事务并将其kCATransactionDisableActions属性设置为true来禁用隐式动画。
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
forKey:kCATransactionDisableActions];
[aLayer removeFromSuperlayer];
[CATransaction commit];
尽可能使用不透明层
将图层的不透明属性设置为YES可以让Core Animation知道它不需要为图层维护alpha通道。没有alpha通道意味着合成者不需要将你的图层的内容与其背景内容混合,这就节省了渲染的时间。但是,这个属性主要与层支持视图的层有关,或者与内核动画创建底层位图的情况有关。如果您直接将图像分配给图层的contents属性,则该图像的alpha通道将被保留,而不考虑不透明属性中的值。
为CAShapeLayer对象使用更简单的路径
CAShapeLayer类通过在合成时将提供的路径呈现为位图图像来创建它的内容。这样做的好处是,该层总是以最好的分辨率绘制路径,但这种好处是以额外的渲染时间为代价的。如果您提供的路径是复杂的,那么栅格化该路径可能会太昂贵。如果层的大小经常变化(因此必须频繁地重新绘制),那么花在绘制上的时间就会增加,从而成为性能瓶颈。
减少形状层绘制时间的一种方法是将复杂的形状分解成更简单的形状。使用更简单的路径,并在合成程序中将多个CAShapeLayer对象层叠在一起,比绘制一个大型复杂路径要快得多。这是因为绘图操作发生在CPU上,而合成发生在GPU上。不过,与任何这种性质的简化一样,潜在的性能收益取决于您的内容。因此,在优化之前测量代码的性能是非常重要的,这样您就有了一个用于比较的基线。
为相同的层显式设置层内容
如果您在多个层对象中使用相同的图像,请自己加载图像并将其直接分配给这些层对象的内容属性。将图像分配给contents属性可防止层为后备存储器分配内存。相反,该层使用您提供的映像作为其备份存储。当多个层使用相同的映像时,这意味着所有这些层共享相同的内存,而不是为自己分配映像的副本。
始终将层的大小设置为整数值
为了获得最佳效果,始终将层对象的宽度和高度设置为整数值。虽然使用浮点数指定层边界的宽度和高度,但层边界最终用于创建位图图像。为宽度和高度指定积分值简化了Core Animation创建和管理后台存储和其他层信息所必须做的工作。
根据需要使用异步层呈现
在你的委托的drawLayer:inContext:方法或视图的drawRect:方法通常在你的应用程序的主线程上同步发生。不过,在某些情况下,同步绘制内容可能无法提供最佳性能。如果您注意到您的动画执行得不是很好,那么您可以尝试在您的层上启用drawsasynchronous属性来将这些操作移到一个后台线程。如果这样做,请确保绘图代码是线程安全的。和往常一样,在将异步绘图放到生产代码之前,应该总是测量它的性能。
当添加阴影到你的图层时,指定一个阴影路径
让核心动画决定阴影的形状可能是昂贵的,并影响你的应用程序的性能。不是让核心动画决定阴影的形状,而是使用CALayer的shadowPath属性显式指定阴影形状。当为该属性指定路径对象时,Core Animation使用该形状来绘制和缓存阴影效果。对于形状从未改变或很少改变的层,这将通过减少核心动画的渲染量而大大提高性能。