实现动画方式深度解析(八) —— Core Animation之动画层内容 (五)

版本记录

版本号 时间
V1.0 2017.09.21

前言

app中好的炫的动画可以让用户耳目一新,为产品增色不少,关于动画的实现我们可以用基本动画、关键帧动画、序列帧动画以及基于CoreGraphic的动画等等,接下来这几篇我就介绍下我可以想到的几种动画绘制方法。具体Demo示例已开源到Github —— 刀客传奇,感兴趣的可以看我写的另外几篇。
1. 实现动画方式深度解析(一) —— 播放GIF动画(一)
2. 实现动画方式深度解析(二) —— 播放GIF动画之框架FLAnimatedImage的使用(二)
3. 实现动画方式深度解析(三) —— 播放序列帧动画(一)
4. 实现动画方式深度解析(四) —— QuartzCore框架(一)
5. 实现动画方式深度解析(五) —— QuartzCore框架之CoreAnimation(二)
6. 实现动画方式深度解析(六) —— Core Animation Basics(三)
7. 实现动画方式深度解析(七) —— Core Animation之Setting Up Layer Objects(四)

Animating Layer Content - 动画层内容

Core Animation提供的基础可以轻松创建应用程序图层的复杂动画,并扩展到拥有这些图层的任何视图。 示例包括更改图层框架矩形的大小,更改屏幕上的位置,应用旋转变换或更改其不透明度。 使用Core Animation,启动动画通常与更改属性一样简单,但您也可以创建动画并显式设置动画参数。

有关创建更高级动画的信息,请参阅Advanced Animation Tricks


Animating Simple Changes to a Layer’s Properties - 动画图层属性的简单更改

您可以根据需要隐式或明确地执行简单的动画。 隐式动画使用默认的时间和动画属性来执行动画,而显式动画需要您使用动画对象自己配置这些属性。 所以隐含的动画比较适合下面情况:你想做一个改变但是没有大量的代码,默认的时间工作就适用于你。

简单的动画涉及更改图层的属性,并让核心动画随着时间的推移动画化这些更改。 图层定义了影响图层可见外观的许多属性。 更改这些属性之一是一种使外观变化动画化的方式。 例如,将层的不透明度从1.0更改为0.0会导致图层淡出并变得透明。

重要提示:尽管有时可以使用Core Animation接口直接对层次支持的视图进行动画处理,但这样做往往需要额外的步骤。 有关如何使用Core Animation与层支持的视图相关的更多信息,请参阅如何使层叠视图生成动画。

要触发隐式动画,您只需要更新图层对象的属性即可。 当修改图层树中的图层对象时,您的更改将立即被这些对象反映出来。 但是,层对象的视觉外观不会立即更改。 反而会发生的是,Core Animation使用您的更改作为触发器来创建和调度一个或多个隐式动画来执行。 因此,进行下面代码所示的更改导致Core Animation为您创建一个动画对象,并计划该动画从下一个更新周期开始运行。

//Animating a change implicitly

theLayer.opacity = 0.0;

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

// 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从图层中删除动画对象,并使用其当前数据值重新绘制图层。 如果要将显式动画的更改设置为永久性,还必须更新图层的属性,如上例所示。

隐式和显式动画通常在当前运行循环循环结束后开始执行,当前线程必须具有运行循环才能执行动画。 如果您更改多个属性,或者将多个动画对象添加到图层,则所有这些属性更改都将同时进行动画处理。 例如,通过同时配置两个动画,您可以在屏幕外移动图层。 但是,您也可以将动画对象配置为在特定时间开始。 有关修改动画时间的更多信息,请参阅 Customizing the Timing of an Animation


Using a Keyframe Animation to Change Layer Properties - 使用关键帧更改图层的属性

基于属性的动画将属性从起始值更改为结束值,CAKeyframeAnimation对象允许您以一种可能是或可能不是线性的方式对一组目标值进行动画化。 关键帧动画由一组目标数据值和每个值达到的时间组成。 在最简单的配置中,您可以使用数组指定值和时间。 对于层的位置的更改,您还可以按照路径进行更改。 动画对象采用您指定的关键帧,并通过在给定时间段内从一个值插值到下一个值来构建动画。

下图展示的是5s的layer position属性变化的动画。

5-second keyframe animation of a layer’s position property

该位置被动画化以遵循使用CGPathRef数据类型指定的路径。 这个动画的代码如下代码所示。

// 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"];

下图示例中的路径对象用于定义动画每帧的层的位置。也就是上个动画中的路径。

1. Specifying Keyframe Values - 指定关键帧的值

关键帧值是关键帧动画中最重要的部分。 这些值定义了动画在其执行过程中的行为。 指定关键帧值的主要方式是对象数组,但对于包含CGPoint数据类型(例如图层的anchorPointposition属性)的值,可以指定CGPathRef数据类型。

当指定值的数组时,放入数组的内容取决于属性所需的数据类型。 您可以直接向数组中添加一些对象; 然而,在添加之前,必须将某些对象转换为id,并且所有标量类型或结构都必须由对象包装。 例如:

  • 对于采用CGRect(例如bounds和frame属性)的属性,将每个矩形包装在NSValue对象中。
  • 对于图层的transform属性,将每个CATransform3D矩阵包装在NSValue对象中。 动画化此属性会导致关键帧动画依次将每个变换矩阵应用于图层。
  • 对于borderColor属性,将每个CGColorRef数据类型转换为类型id,然后再将其添加到数组。
  • 对于采用CGFloat值的属性,将NSNumber对象中的每个值都包含到数组中。
  • 当动画化图层layer的content属性时,指定一个CGImageRef数据类型的数组。

对于采用CGPoint数据类型的属性,可以创建一个点阵列(包含在NSValue对象中),也可以使用CGPathRef对象来指定要遵循的路径。 当您指定点数组时,关键帧动画对象在每个连续点之间绘制一条直线,并跟随该路径。 当指定CGPathRef对象时,动画从路径的起点开始,并按照其轮廓,包括沿着任何曲面。 您可以使用打开或关闭的路径。

2. Specifying the Timing of a Keyframe Animation - 指定关键帧动画的时序

关键帧动画的时间和步进比基本动画更复杂,有几种属性可用于控制它:

  • calculateMode属性定义了用于计算动画时序的算法。 该属性的值影响其他与时间相关的属性的使用方式。

    • 线性和立方体动画 - 即将calculateMode属性设置为kCAAnimationLinearkCAAnimationCubic的动画 - 使用提供的时间信息来生成动画。 这些模式可以让您最大限度地控制动画的时间。
    • 步进动画 - 即将calculationMode属性设置为kCAAnimationPacedkCAAnimationCubicPaced的动画不依赖于keyTimestimingFunction属性提供的外部定时值。 相反,定时值被隐式计算,以提供恒定速度的动画。
    • 离散动画 - 即将calculationMode属性设置为kCAAnimationDiscrete的动画 - 使动画属性从一个关键帧值跳转到下一个,而不进行任何插值。 此计算模式使用keyTimes属性中的值,但忽略timingFunctions属性
  • keyTimes 属性指定应用每个关键帧值的时间标记。 仅当计算模式设置为kCAAnimationLinearkCAAnimationDiscretekCAAnimationCubic时才使用此属性。 它不用于paced动画。

  • timingFunctions属性指定要用于每个关键帧段的时序曲线。 (此属性替代继承的timingFunction属性。)

如果要自己处理动画时间,请使用kCAAnimationLinearkCAAnimationCubic模式以及keyTimestimingFunction属性。 keyTimes定义应用每个关键帧值的时间点。 所有中间值的时序由定时timing functions控制,允许您对每个段应用简单易用的曲线。 如果没有指定任何时间函数,时序是线性的。


Stopping an Explicit Animation While It Is Running - 在运行时停止显式动画

动画通常运行直到它们完成,但是如果需要,可以使用以下技术之一来阻止它们:

  • 要从图层中删除单个动画对象,请调用图层的 removeAnimationForKey:方法来删除动画对象。 此方法使用传递给 addAnimation:forKey:方法来标识动画的键。 您指定的密钥不能为零。

  • 要从图层中删除所有动画对象,请调用图层的removeAllAnimations方法。 此方法立即删除所有正在进行的动画,并使用其当前状态信息重新绘制图层。

注意:您不能直接从图层中删除隐式动画。

当您从图层中删除动画时,Core Animation会通过使用其当前值重新绘制图层来进行响应。 因为当前值通常是动画的最终值,这可能会导致图层的外观突然跳过。 如果您希望图层的外观保留在动画的最后一帧上,则可以使用展示树中的对象来检索最终的值,并将其设置在图层树中的对象上。


Animating Multiple Changes Together - 多个变化的动画化

如果要同时对图层对象应用多个动画,可以使用CAAnimationGroup对象将它们分组在一起。 使用组对象通过提供单个配置点来简化多个动画对象的管理。 应用于组的时间和持续时间值会覆盖单个动画对象中的相同值。

下面代码显示了如何使用动画组在同一时间和持续时间内执行两个与边框相关的动画。

//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"];

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


Detecting the End of an Animation - 动画结束的监测

核心动画提供了检测动画何时开始或结束的支持。 这些通知是与动画相关联的任何内务处理任务的好时机。 例如,您可以使用启动通知来设置一些相关的状态信息,并使用相应的结束通知来拆除该状态。

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

  • 使用setCompletionBlock:方法将完成块添加到当前事务。 当事务中的所有动画完成时,事务执行完成块。
  • 将代理分配给您的CAAnimation对象并实现animationDidStart:animationDidStop:finished:代理方法。

如果要将两个动画链接在一起,以便在其他完成时启动,请勿使用动画通知。 相反,使用动画对象的beginTime属性可以在所需的时间启动每个动画对象。 要将两个动画连接起来,将第二个动画的开始时间设置为第一个动画的结束时间。 有关动画和时间值的更多信息,请参阅 Customizing the Timing of an Animation


How to Animate Layer-Backed Views - 如何将layer层支持的view动画化

如果图层属于图层支持视图,建议使用创建动画的方法是使用UIKit或AppKit提供的基于视图的动画界面。 有一些方法可以直接使用Core Animation界面来动画化图层,但是如何创建这些动画取决于目标平台。

1. Rules for Modifying Layers in iOS - iOS修改图层的规则

因为iOS视图总是具有底层,所以UIView类本身直接从图层对象中导出大部分数据。 因此,您对图层所做的更改也将自动反映在视图对象中。 此行为意味着您可以使用Core AnimationUIView界面进行更改。

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

//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"];

}];

2. Rules for Modifying Layers in OS X - OS X中修改图层的规则

要在OS X中对层次支持的视图进行动画更改,最好使用视图本身的接口。 您很少(如果有的话)直接修改附加到其中一个层支持的NSView对象的图层。 AppKit负责在应用程序运行时创建和配置这些图层对象并进行管理。 修改图层可能导致它与视图对象不同步,并可能导致意想不到的结果。 对于层次支持的视图,您的代码绝对不能修改图层对象的以下属性:

重要提示:上述限制不适用于图层托管视图。 如果您创建了图层对象并将其与视图手动关联,则负责修改该图层的属性并保持对应的视图对象同步。

默认情况下,AppKit会禁用其层次支持的视图的隐式动画。 视图的动画师代理对象为您自动重新启用隐式动画。 如果要直接对图层属性设置动画,您还可以通过将当前NSAnimationContext对象的allowedImplicitAnimation属性更改为YES来以编程方式重新启用隐式动画。 同样,您应该只对不在上述列表中的动画属性执行此操作。

3. Remember to Update View Constraints as Part of Your Animation - 记住将视图约束更新为动画的一部分

如果您使用基于约束的布局规则来管理视图的位置,则必须删除可能干扰动画的任何约束,作为配置该动画的一部分。 约束影响对视图的位置或大小所做的任何更改。 它们也影响视图与其子视图之间的关系。 如果您正在对任何这些项目进行动画更改,您可以删除约束,进行更改,然后应用所需的新约束。

有关约束的更多信息以及如何使用它们来管理视图的布局,请参阅 Auto Layout Guide

后记

未完,待续~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容