动画的原理

什么是动画
动画,顾名思义,就是能“动”的画。人的眼睛对图像有短暂的记忆效应,所以当眼睛看到多张图片连续快速的切换时,就会被认为是一段连续播放的动画了。
比如,中国古代的“走马灯”,就是用的这个原理。有些人还会在一个本子每页上手绘一些漫画,当快速翻页的时候,也会看到动画的效果,比如:


图片来自网络

计算机动画的实现方式
动画是由一张张图片组成的,在计算机中,我们称每一张图片为 一帧画面
如果我们想实现这么一个动画:一个水杯放在桌子的左边,移动到右边,那么我们实际操作的,只是水杯。所以动画的实现,只是对运动变化了的部分的处理。
逐帧 与 关键帧
类似于上面提到的手绘翻页方式,我们可以将这个水杯在每帧画面中的位置一一找出来,这样实现动画的方式就叫作 逐帧动画,我们需要处理动画中的每一帧。
我们一般在计算机上用 FPS ( Frames Per Second) ,即 每秒的帧数 来表示动画的刷新速度,基于屏幕的刷新率等其他原因,在计算机上一般采用 60 FPS。如果运动变化幅度较缓,减半到 30 FPS 时,我们肉眼也是可接受的。较低的 FPS 会让我们有“卡顿”的感觉。

逐帧动画是最直接的,但要处理的帧数太多,所以实现过程是会麻烦。
计算机的工作就是来完成重复单调的工作的,所以,有些工作是可以考虑让计算机来完成的。
上面的例子,可以变成一个涉及数学和物理的问题:一个杯子初始位置在左边,n秒后匀速运动到右边,那么在每 1/60 秒的时候,这个杯子的位置显然是可以计算出来的了。所以,我们其实只需要指定一些 关键 信息就能让计算机自己计算出每一帧杯子的位置了:
起始位置,比如一个坐标 (0,0)
结束位置,再比如一个坐标 (100,0)
动画总时间,比如 0.25 秒
匀速运动

这种方式就称之为 关键帧动画。即我们只需要给定几个关键帧的画面信息,关键帧与关键帧之间的过渡帧都将由计算机自动生成。
这里说的 关键帧动画,是指的广义上的一种动画制作方式,并不仅指CAKeyframeAnimation
,CABasicAnimation
的实现方式也属于 关键帧动画

iOS 动画
说完广义上的动画,就可以来说说 iOS 的动画了。先来说说动画的本质。
动画的本质
继续用上面的简单例子:一个UIView
从 (0,0) 匀速移动到 (100,0)的动画,动画总时间是0.25秒。假设我们基于 60 FPS 来显示动画,那么在0.25秒内就应该有15帧画面,在每帧画面中,这个UIView
x坐标,每次应移动 100/15 的距离。如果我们每隔 0.25/15 秒刷新一次UIView
x坐标,那么就能实现这个动画效果了。对于 x坐标而言,每帧的位置就可以通过一个基于时间变化量的函数来求得:x=f(t)
所以,一个动画的本质,就是动画对象(这里是UIView
)的状态,基于时间变化的反应了。简单说,就是给定任意一个时刻,如果你都能得到这个动画对象的位置和、形状等等属性,你就能实现这个动画了。属性值的变化,既可能是位置、透明度、旋转角度等的变化,也包括形状的改变,比如从一条直线变化成一个圆圈,目标就是要得到变化过程中特定时刻的中间态。
动画的实现
我们也可将 iOS 的动画分为两大类:
系统提供的 关键帧动画 实现方式;用户指定 关键 信息,系统实现动画过程,对用户而言操作起来会简单些。
逐帧动画 实现方式;用户自己 出每一帧画面,系统操作方法简单,但用户操作的工作量就会大一些。

逐帧动画实现方式
简单的说,要实现逐帧的方式,就是需要 周期性 的调用 绘制 方法,绘制每帧的动画对象。
这里说的 绘制,不光是指覆写UIView
的- drawRect:
的方法来手动重绘视图,也包括修改UIView
它的属性,比如位置、颜色等。
iOS 的动画都是基于CALayer
的,iOS 的UIView
背后都有一个对应的CALayer
。对UIView
的修改实际上都是对背后CALayer
的修改。但如果在逐帧绘制的方法中修改了一个自建的CALayer
,这个CALayer
不是对应某个UIView
的,需注意系统的 隐式动画 的影响,后面会提到这点。

周期性,就需要一个定时器来完成了,即CADisplayLink
。CADisplayLink
与NSTimer
比较类似,可以周期性的调用指定的方法。之所以用CADisplayLink
,是因为它是基于屏幕刷新率的,即屏幕每次刷新时就会触发调用。iPhone 的屏幕刷新率是 60 FPS。
如果绘制过程过于复杂,不能在屏幕刷新一帧的时间内完成,可以考虑改为每隔一帧绘制,相当于是 30 FPS的刷新率。不然可能会使动画不连贯,有卡顿感。

用逐帧方法绘制的原理不是很麻烦,麻烦的是绘制过程。对于一个复杂动画,你可能需要运用各种物理、几何知识去计算视图中间状态的信息。比如要实现一条直线卷曲变化为一个圆的动画,你就需要计算出中间态的曲线的弯曲程度和位置。
著名的 facebook 的 pop 动画框架,就是使用CADisplayLink
这种逐帧绘制的方式实现的。
关键帧动画实现方式
采用关键帧的方式来实现动画,要讲的内容相对逐帧的方式就多的多了。
还是用UIView
移动的简单例子。这里面有两个关键帧,起始帧和结束帧,除此之外还有2个关键信息:
起始帧,变化信息:坐标为 (0,0)
结束帧,变化信息:坐标为 (100,0)
动画时间,0.25秒
匀速运动

坐标 信息是UIView
的一个属性(实际是对应到CALayer
的属性),在动画实现里,我们只需要指定起始和结束的两个关键值就够了,中间的过渡值都有系统自动生成。这里出现了两种值,一个是我们设定的,一个是系统生成的,所以要先在这里插入一个 模型层展现层 的概念了
CALayer
的同一个属性值,会分别保存在模型层 modelLayer ,和展现层 presentationLayer 中。当我们修改属性值时,是修改的模型层的数值,动画时系统根据模型层的变化,生成的过渡值,是保存在展现层中的。
在CALayer
的对象里能直接访问到这两层的信息。而CALayer
的底层实现实际不止这两层,但我们现在讨论动画的时候,可以只关心这两层。

在整个动画过程中,呈现出来的过程是这样的:
动画前,显示模型层的当前值;
动画开始,切换显示展现层的值;
动画过程中,展现层的值根据时间变化,我们看到的实际是展现层的值在变化;
动画结束,切换回显示模型层的值,此时模型层的值应被修改为动画结束时的值。

用一段代码来解释下动画过程。

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; 
view.backgroundColor = [UIColor redColor];
 [self.view addSubview:view]; 
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
 animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(50, 0)]; 
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(150, 0)]; 
[view.layer addAnimation:animation forKey:nil];
// view.frame = CGRectOffset(view.frame, 100, 0);

你会发现动画结束后,view
又跳回了原来的位置,这是因为最后一行代码注释了,而这行代码的功能就是实现第4步,将模型层的值修改为动画结束时的值。
动画实现
代码中的CABasicAnimation
就是真正的动画实现部分,也就是设定关键帧信息的地方。
将动画加入CALayer
的代码定义为:- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key
接受的类型是CAAnimation
类型,有下面这些子类:
CABasicAnimation
,可设定起始结束两个关键帧的信息。
CAKeyframeAnimation
,除首尾外,还可添加多个中间关键点。
CAAnimationGroup
,可组合多个动画,因为上面两种动画一次只能设置一个属性值。
CATransition
,图层过渡动画,默认是淡入。比如修改一个CALayer
的背景色时,是从初始色慢慢淡入过渡到结束色。可修改为新颜色把旧颜色顶出去等效果。还可使用CIFilter
滤镜做过渡效果,一些开源UIViewController
的过渡动画使用了这种方式。

动画中,除了属性值外,我们还设置了两个和时间有关的信息:动画时间0.25秒,运动方式是匀速运动。
动画持续时间很简单,是通过CAAnimation
遵守的CAMediaTiming
协议设定的。
匀速运动是通过设置CAAnimation
的timingFunction
实现的,这是一个CAMediaTimingFunction
类的对象。
之前已经说到,动画过程实际是一个时间的函数,横坐标是时间的变化值,纵坐标是动画属性的变化量。那么我们就可以在一个直角坐标系中,通过作图来画出这个函数。比如匀速运动的图形,就是一条通过原点的直线。
所以这个类的功能就是画出一条曲线,来表示时间和属性变化之间的关系。而画图的方法,是使用的是画贝叶斯曲线的方法。
系统提供了几个常用的函数,比如kCAMediaTimingFunctionLinear
就是匀速运动;kCAMediaTimingFunctionEaseInEaseOut
就是一般系统动画的默认值,渐入渐出,即在动画开始和结束的时候速度稍慢些。


图片来源自网络

隐式动画
上面的过程,我们是 显式 的向一个CALayer
添加了一个动画,所以这种方式叫做 显式动画。对应的,还有 隐式动画,即系统自动添加上的动画。
CALayer *layer = [CALayer layer]; layer.backgroundColor = [UIColor greenColor].CGColor; layer.frame = CGRectMake(0, 0, 100, 100); [self.view.layer addSublayer:layer]; layer.frame = CGRectOffset(layer.frame, 100, 0);

这段代码里,我们没有添加CAAnimation
动画,但 layer 不是直接变化到新的位置,而是有一个动画效果。这就是 隐式动画 的效果。
当我们改变CALayer
的一个可动画的属性值时,就会触发系统的隐式动画。可动画的属性值,可以在CALayer
的文档中找到,属性说明中标有 ** Animatable** 的,就是可自动添加动画的属性。
但是,有一个例外,对于UIView
背后对应的CALayer
,系统关闭了隐式动画,所以当我们直接修改UIView
或者是其底层的CALayer
时,变化是直接生效的,没有动画效果。
所以当我们在逐帧方式生成动画时,是可以直接修改UIView
或者是其底层的CALayer
的信息。但是如果修改的是一个自建的单独CALayer
时,帧与帧之间的变化还是会触发系统的默认隐式动画,这个时候就需要我们来手动关闭隐式动画。当快速动画的时候不会察觉到这点,但这明显会带来性能上的浪费。

隐式动画所做的事情和显示动画是一样的,我们设置的属性值都是模型层的数值,而系统会自动添加属性对应的CAAnimation
动画到CALayer
上。
UIView
有一系列的animateWithDuration
动画方法,在这些方法中UIView
会恢复隐式动画,所以在动画的 block 中修改属性时,又会触发隐式动画。

那么系统是如果知道对一个属性应该添加哪种动画呢,这就需要让CAAction
协议登场了。
当修改一个CALayer
的属性时,它会通过- actionForKey:
来查询这个属性对应的 action,而 key 就是对应的属性名称。CAAnimation
遵守CAAction
协议,返回的 action 其实是个CAAnimation
动画。也就是说,CALayer
通过- actionForKey:
来查询某个属性被修改时,需要调用哪个动画去展现这个变化。一般默认返回的是CABasicAnimation
,默认动画时间 0.25秒,时间函数为渐入渐出 kCAMediaTimingFunctionEaseInEaseOut。

  • actionForKey:
    查询 action 的步骤有4步,在这个方法中有详细的说明。其中一种方式就是通过CALayer
    的 delegate 返回 action。而对于UIView
    背后对应的CALayer
    ,其代理就是它对应的UIView
    ,UIView
    就是用这种方式关闭了隐式动画。

动画事务
创建动画事务的目的是为了操作的原子性,保证动画的所有修改能同时生效。CATransaction
就是动画事务的操作类。
在创建隐式动画的时候,系统也会隐式的创建一个动画事务,以保证所有的动画能同时进行。
除此之外,还可以显式的创建一个事务。显式事务中可以定义事务中所有动画的运行时间和时间函数,此外,还有这个方法+ (void)setDisableActions:(BOOL)flag
能显式的关闭这个事务中的 action 查询操作。关闭了查询也就是关闭了动画效果,属性值的变化就会立即生效,而没有动画效果了:
[CATransaction begin]; [CATransaction setDisableActions:YES]; ///... layer.frame = CGRectOffset(layer.frame, 100, 0); ///... [CATransaction commit];

注意别把 CATransaction 和 CATransition 搞混了,一个单词是 transaction 事务,另一个是 transition 转变。

对比 总结
关键帧动画的实现方式,只需要修改某个属性值就可以了,简单方便,但涉及的深层次内容较多,需要更多的理解和练习。
采用逐帧动画的实现方式,实现原理简单,但绘制动画的过程要复杂。如果动画过程处理的事情较多,也会带来较大的开销,就有可能造成动画帧数的下降,出现卡顿的现象,因此需要较多的测试和调试。动画绘制的过程中,会要求较多的数学、物理等知识来计算中间态的数据。
但这两种方式也不是绝对分离开的。关键帧动画实现方式,一般只能对系统实现了可动画的属性做动画处理,但其实也是允许实现自定义属性的动画处理的。这就需要自己来实现系统中自动计算过渡帧的操作了,也就是逐帧实现动画的方式了。实现自定义属性的动画可以参考这篇文章: Layer 中自定义属性的动画
对于 iOS 系统提供的动画方法,上面只是从整体的角度作了一个全面的整理,还有很多细节内容没有写出来,比如CALayer
的三维变换、CAKeyframeAnimation
的延路径动画,CAMediaTiming
的时间控制,等等。感兴趣的话,可以再看看这些内容:
苹果官方文档 Core Animation Programming Guide
iOS Core Animation: Advanced Techniques -- 中文翻译
objc中国 动画专题 中的 动画解释

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

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,465评论 6 30
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,094评论 5 13
  • 什么是动画 动画,顾名思义,就是能“动”的画。 人的眼睛对图像有短暂的记忆效应,所以当眼睛看到多张图片连续快速的切...
    CoderSC阅读 1,299评论 0 1
  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 3,078评论 1 23
  • 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你...
    Yiart阅读 3,792评论 3 34