Core Animation 文档翻译 (第四篇)
前言
核心动画的基础接口以及为拥有Layer的View做的动画扩展接口,使得为Layer制作复杂动画变得简单化。例如改变Layer的frame的size、改变Layer在屏幕上的position、应用旋转transform、或者改变它的opacity。通过使用核心动画,创建一个动画效果将会变得简单的就像修改属性一样,但是我们也能显式的创建和设置动画参数。
关于创建更多高级动画可以参见Advanced Animation Tricks(后续会有译文)。
Layer属性改变的动画
根据需要,我们可以选择显式或者隐式的执行简单的动画。隐式的动画使用默认的速度调节和动画属性来执行动画;相反,隐式动画需要我们使用核心动画对象配置相应的属性。因此当我们想要使用少量代码和默认定时速度做动画过度改变的时候,隐式动画是个不错的选择。
简单的动画意味着改变Layer的属性,和让核心动画是这些改变随着时间发生变化。Layers定义了许多能够影响可视化外观的属性,改变这些属性之一就会引起相应的外观动画性的变化;例如,改变Layer的opacity由1.0到0.0将会引起Layer渐渐隐藏(淡出)并变透明。
重要提示:我们有时可以直接使用核心动画的接口为Layer-backed View(iOS 内的view都可称为此种view,前面章节有说明)做动画,这么做常常需要一些额外的步骤,更多关于如何使用与Layer-backed views结合的动画可以参见如何为Layer-Backed View做动画。
为了触发隐式动画,我们只需要去更新Layer对象的属性。当更改图层树上的Layer对象的时候,Layer的属性将会立刻变更为我们调整的值,然而Layer对象的外观不会立刻改变为我们设置的值;相反,核心动画使用我们调整的值作为触发器来创建和规划一个或多个隐式动画,然后核心动画并执行这些动画。因此,像代码3-1中调整属性值将会引起核心动画创建动画对象,并规划那个动画到下个更新循环开始执行。
Listing 3-1 Animating a change implicitly
theLayer.opacity = 0.0;
为了制作和隐式动画一样的动画,我们也可通过显示的使用动画对象,创建一个CABasicAnimation 对象和使用那个对象配置动画参数。在将动画对象添加到Layer上之前,我们可以为动画对象设置开始和结束值、改变动画周期,或者任何其他的动画参数。代码3-2展示了如何通过动画对象将Layer渐隐。当创建动画对象后,我们指定要做动画的属性的Key path,然后设置动画参数。为了执行动画,我们使用addAnimation:forKey:方法将动画对象添加到Layer上。
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属性值,如果我们不指定这个值,核心动画将会使用Layer的当前值作为动画的开始值,如果我们将这个属性值设定的和动画的最终值一致,那么将不会产生我们想要的动画效果。
和隐式动画不一样的是,隐式动画将会更改Layer对象的值,显示动画不会修改图层树上的数据。显示动画仅仅产生动画效果。在动画的结束后,核心动画将会从Layer上移除动画对象并使用Layer的当前值重新绘制。如果我们想要显示动画的改变发生到Layer上,那么我们必须也更新Layer的属性,就像3-2中指出的一样。
隐式和显示动画通常在当前runloop循环结束后就开始执行,为了动画对象的执行,当前线程必须有一个runloop。如果我们改变多个属性,如果为Layer添加多个动画对象,所有这些属性将会同时发生动画。例如我们通过同时设置两个动画,就可以使得隐藏一个Layer和将Layer移动到屏幕外面的同时发生。我们一可以设置动画对象在某个特殊的时间点开始。关于更多的修改动画的时间函数参见 Customizing the Timing of an Animation(后续会有译文)。
使用关键帧动画改编Layer的属性
属性动画通过改编属性从开始值到结束值发生动画, CAKeyframeAnimation 对象将会通过设定线性或者非线性目标值点集合的方式制作动画。一个关键帧动画由目标集合点,和与单个目标点对应的时间点的集合组成。最简单的配置就是,我们仅仅通过使用数组指定这两种值。对于改变Layer的position来说,我们也可以指定它沿着path发生变化。动画对象取我们指定的关键帧并通过一个值到下一个值在给定的时间片刻上进行插值建立动画。
图3-1显示了Layer的position属性5s的动画。Position是沿着一条path做的动画,就是使用CGPathRef数据类型制定的path。对应的代码见代码3-3。
Figure 3-1 5-second keyframe animation of a layer’s position property
代码3-3展示了如何实现图3-1中动画的代码。在这个例子中,path对象是被用来定义每帧动画中Layer的position。
Listing 3-3 Creating a bounce keyframe 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);
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"];
指定关键帧的值
关键帧的值是关键帧动画最重要的一部分。关键帧的值明确了动画执行的路线。主要指定关键帧值的方式是使用数组,但是对于当数组中包含CGPoint数据类型的时候(例如Layer的anchorPoint和position属性),我们也可以指定CGPathRef数据类型代替。
当指定数组值的时候,数组中应该放的数据类型取决于要做动画的属性。我们可以将某些对象直接添加到数组中;但是有些类型必须先转换到id对象类型在添加之前,所有的标量类型和结构体必须先包装成对象,例如:
- 对于对应CGRect的属性(例如bounds和frame属性),需要一NSValue对象包装每个值。
- 对于Layer的transform属性,则需要将CATransform3D矩阵包装为NSValue对象。动画这个属性引起关键帧动画按顺序将每个transform矩阵变换应用到Layer上。
- 对于borderCorlor属性,在加入数组前,需要将每个CGColorRef数据类型映射为id类型。
- 对于对应CGFloat类型的属性,再加入数组前,需要将每个值包装为NSNumber对象。
- 当要为Layer的contents属性做动画的时候,数组中需要添加CGImageRef类型的数据。
对于对应CGPoint数据类型的属性,我们可以创建point的数组(先包装成NSValue对象)或者使用CGPathRef对象指定动画的路线。当我们指定point的数组的时候,关键帧动画对象创建的动画将会走直线在相邻的点。当我们指定CGPathRef对象,动画从路径的起点开始,并沿着他的轮廓走,如果是曲线,它也能够沿着曲线的路径走。我们也可以使用非闭合或者闭合的路径。
指定关键帧动画的时间函数
关键帧的时间函数和空间位置是比基本动画(basic animations)更复杂的,这里有几个供控制它们的属性:
- calculationMode属性用来指定计算动画函数的算法。它的值将影响其他和时间相关的属性如何变化。
- 线性和多阶动画——当动画calculationMode属性被设置为kCAAnimationLinear 或 kCAAnimationCubic时,将启用被提供的时间(timeing)的信息生成动画,这种模式将会为我们提供对于动画时间函数最灵活的控制。
- 平缓的动画——当动画calculationMode属性被设置为kCAAnimationPaced或kCAAnimationCubicPaced时,动画将不依赖keyTimes或timingFunctions属性额外提供的时间函数值。相反的时间函数的插值将会以一个常量速度隐式的计算得出。
- 离散动画——当动画calculationMode属性被设置为kCAAnimationDiscrete时,此时动画将会在没有任何插值的情况下,从一个关键帧跳到下一个关键帧。这个计算模式使用keyTimes属性里面的值,但是忽略timingFunctions属性。
- keyTimes 属性指定时间标记,对应于这些时间标记值会应用到每一帧的值。仅仅当calculationMode属性被设置为 kCAAnimationLinear, kCAAnimationDiscrete, or kCAAnimationCubic,这个属性才会被才用。对于平缓动画它是无用的。
- timingFunctions属性将使用时间函数曲线制作每一帧。(该属性将会取代父类的timingFunction属性。)
如果我们想要自己控制时间函数,需要采用 kCAAnimationLinear或kCAAnimationCubic 模式和keyTimes以及timingFunctions属性。keytimes属性指定应用于对应帧值的时间点。timeingFunctions用于控制所有时间的插值,它将允许在每个片段我们应用缓入或者缓出。如果我们不指定timingFunctions时间函数将会是线性的。
移除显示的动画(当显示动画在执行过程中)
正常情况下动画会运行到动画完成,但是如果有需要,我们能够通过以下方式提前移除它们:
- 从Layer上移除单个动画对象,调用Layer的removeAnimationForKey: 方法以便移除动画对象。这个方法需要使用之前被提供到 addAnimation:forKey:方法的key,因为再添加动画的时候key就在这个Layer上对应到该动画对象。key不能为nil。
- 从Layer上移除所有的动画对象,只需要调用Layer的removeAllAnimations方法。这个方法将会立即移除所有正在进行的动画并使用当前状态信息重绘Layer。
注意:我们不能从Layer直接移除隐式动画。
当我们从Layer上面移除动画的时候,核心动画将会响应——使用Layer当前的值重绘Layer。因为通常是动画的最终值(end values),这可能引起Layer的样貌发生跳跃性的变化。如果我们想要Layer的样貌保持在动画的最后一帧,我们可以使用呈现树上的Layer对象获取最终值(如果是移除操作也可以称为,动画当前值)并设置他们这些值到图层树。
更多关于临时暂停动画可参见代码5-4(后续会有译文)。
将多个调整绑定在一起做动画
如果我们想要将多个动画同时应用到Layer对象,我们可以将他们绑定在一起通过使用CAAnimationGroup对象,group对象可以以简单的配置简化多个动画对象的管理。设置到group对象的时间函数和duration值将会覆盖单个动画对象对应的值。
代码3-4展示了如何使用一个group对象以相同的时间函数和相同时间周期的实现两个边框相关的动画。
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对象能够提供将动画组合在一起的更高级方式。Transcation通过允许我们创建嵌套的动画集和为每个动画关联不同动画参数,也因此Transcation更灵活,更多关于如何使用Transaction对象,可参见 Explicit Transactions Let You Change Animation Parameters(后续会有译文)。
获悉动画的开始和结束时机
核心动画提供获取动画开始和结束事件的支持。这些通知对于动画辅助任务来说是很好的时间点。例如我们可以借助获取到开始通知时去设置一些相关联的状态信息并使用对应的结束通知撤销这些状态。
有一下两种不同的获取方式:
- 添加完成的block到当前的transaction通过使用setCompletionBlock:方法。当transaction中所有的动画结束的时候,transaction就会执行我们的完成block。
- 为CAAnimation对象关联代理并实现 animationDidStart:和animationDidStop:finished:代理方法。
如果我们想要去将两个动画衔接在一起,实现当一个结束另外一个就开启,不要使用动画通知,相反的应该使用动画对象的beginTime属性去开启每一个动画在期望的时间点。为了将两个动画前后衔接在一起,设置第二个动画的start为第一个动画的end时间。更多关于动画和时间值的信息参见Customizing the Timing of an Animation(后续会有译文)。
如何为Layer-Backed View做动画
如果Layer属于Layer-backed view,动画创建的推荐方式是使用通过UIKit或AppKit提供的基于View的动画接口。通过使用核心动画接口,有许多方式可以为Layer做动画,但是如何创建这些动画取决于所在的目标平台(iOS 、OSX)。
在iOS上修改Layer的规则
因为iOS中的view一直都有一个内在的Layer,UiView类直接从layer对象直接获取许多数据,这就导致我们为Layer做的调整也会在View对象上自动提现出来,这就意味着我们可以使用核心动画或者UIView的接口来实现这些调整。
基于Layer-backed View,如果我们想要使用核心动画创建动画,我们必须在基于view的动画block内部发起所有的核心动画的调用。UIView类默认禁用了Layer动画,但是在动画bolcks中可以激活,因此任何在bolcks外的Layer调整将不会发生动画。代码3-5展示了基于Layer-backed View内如何为Layer制作opacity隐式动画,和position显示动画。在这个例子中,myNewPosition变量是被提前计算并被block捕获到的。两个动画在同一时间开始,但是opacity动画一默认的时间函数运行,position动画以他动画对象所指定的时间函数运行。
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"];
}];
作为动画的一部分,记着更新View的约束
如果使用基于约束的布局规则管理views的position,那么作为配置动画的一部分,我们必须移除任何影响动画的约束。约束影响任何我们为view的position或size做的调整,他们常常影响view之间以及view和他们子view的关系;如果我们为这些带有约束的view做动画,我们需要移除这些约束,并应用我们需要的新的约束。
更多关于约束和如何使用他们管理views的layout的信息参见Auto Layout Guide。
Core Animation 文档翻译 (第四篇)的更多相关文章
- Core Animation 文档翻译 (第一篇)
Core Animation 文档翻译(第一篇) 2018-01-13 星期6 前言:作为iOS 开发,官方文档的阅读是很有必要的,值此周末便写下此文.作为iOS 实际经验3年的开发,之前的应用 ...
- Core Animation文档翻译 (第一篇)
Core Animation 文档翻译(第一篇) 前言 作为iOS 开发,官方文档的阅读是很有必要的,值此周末便写下此文.作为iOS 实际经验3年的开发,之前有阅读并实践过经典的<iOS核心动画 ...
- Core Animation 文档翻译 (第二篇)
Core Animation 文档翻译 (第二篇) 核心动画基础要素 核心动画为我们APP内Views动画和其他可视化元素动画提供了综合性的实现体系.核心动画不是我们APP内Views的替代品,相反, ...
- Core Animation 文档翻译(第三篇)
Core Animation 文档翻译(第三篇) 设置Layer对象 当我们使用核心动画时,Layer对象是一切的核心.Layers 管理我们APP的可视化content,Layer也提供了conte ...
- Core Animation 文档翻译 (第六篇)
高级动画技巧 配置属性动画或者关键帧动画的方式是多种多样的.需要同时执行多个动画或者顺序执行多个动画的APP,可以通过高级的方式同步这些动画的timing或者将这些动画绑定在一起.我们也可以使用其 ...
- Core Animation 文档翻译 (第八篇)—提高动画的性能
前言 核心动画是提高基于APP动画帧率的好方式,但是核心动画的使用不代表性能的提升的保证.尤其在OSX,当使用核心动画时,我们仍需选择最有效的方式.和所有的性能相关的问题一样,我们应该使用工具时时的评 ...
- Core Animation 文档翻译 (第五篇)
构建Layer层次结构 在APP中大多数情况下,将Layer和View对象结合使用是Layer最好的使用方式.然而,很多时候我们可能需要通过添加单独的Layer对象,以便增加视图继承层次:当为了提 ...
- Core Animation 文档翻译 (第七篇)——改变Layer的默认动画
前言 核心动画使用action对象实现它的可视化动画.一个action对象是指遵循CAAction协议并定义了Layer相关的动画行为的对象.所有的CAAnimation对象实现了这个协议,无论何时L ...
- Core Animation 文档翻译—附录B(可动画的属性)
前言 许多CALayer和CIFliter的属性都是可动画的.本节附录列出了这些属性默认使用的动画. CALayer可动画属性 表B-1展示了CALayer类的可动画属性.针对每个属性此表 ...
随机推荐
- ------- 当前全球最新的 IPv4 地址池使用报告 -------
-------------------------------------------------------------- 对于互联网行业相关的从业人员而言,时刻关注 IPv4 地址池的状态此类&q ...
- Linux下安装opencv模块
最近微信上流行的给自己的头像加一顶圣诞帽,想用python写一个程序自己实现一下,其中需要用到opencv import cv2 现在记录一下如何在Linux系统(ubutun)下安装该模块: 参考了 ...
- Elastic FileBeat 快速入门
背景 用过ELK(Elasticsearch, Logstash, Kibana)的人应该都面临过同样的问题,Logstash虽然功能强大:支持许多的input/output plugin.强大的fi ...
- 浴室沉思:聊聊DAL和Repository
这是一个由DDD群引发的随笔 在写了上一篇随笔<关于ORM的浴室沉思>后一些朋友私聊我,很多刚接触DDD的朋友会对Repository(仓储层)这东西有点疑惑,为什么要叫仓储层?是不是三层 ...
- BZOJ 3668: [Noi2014]起床困难综合症【贪心】
3668: [Noi2014]起床困难综合症 Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 2326 Solved: 1305[Submit][St ...
- AtCoder Regular Contest 069 D
D - Menagerie Time limit : 2sec / Memory limit : 256MB Score : 500 points Problem Statement Snuke, w ...
- 【Java学习笔记之十四】Java中this用法小节
用类名定义一个变量的时候,定义的只是一个引用,外面可以通过这个引用来访问这个类里面的属性和方法. 那们类里面是够也应该有一个引用来访问自己的属性和方法纳? 呵呵,JAVA提供了一个很好的东西,就是 t ...
- HDU 2289 Cup【高精度,二分】
Cup Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submiss ...
- [51nod1440]迈克打电话
有n只熊,从1到n进行编号. 第i只熊的电话号码是si.每只熊会给那些电话号码是他的子串的熊打电话(可能会给自己打). call(i, j) 表示第i只熊给第j只熊打电话的次数,也就是第j个串在第i个 ...
- itoa函数,sprintf函数
itoa函数 itoa 为c语言的一个函数.itoa 函数是一个广泛应用的,从非标准扩展到标准的C语言.它不能被移植,因为它不是标准定义下的C语言,但是,编译器通常在一个不遵循程式标准的模式下允许其通 ...