Core Animation的学习记录

层次

Core Animation是不会修改CALayer的属性的,它维护了三个平行的layer层次结构: model tree(模型层树)、presentation tree(表示层树)、rendering tree(渲染树).模型层反应了我们能直接看到的layer的状态,表示层是动画正在表现的状态的近似值,渲染层对Core Animation是私有的。

隐式动画

当我们修改CALayer的那些Animatable的属性时,会产生一个隐式动画,这个动画是从- (nullable id<CAAction>)actionForKey:(NSString *)event方法中返回的,一般会是一个CAAnimation对象。
是的,CAAnimation遵守了CAAction协议。

这个方法的查找顺序是:

  1. 调用代理(如果有)的-actionForLayer:forKey:方法
  2. layeractions字典中查找
  3. style字典中查找actions字典来获取
  4. 调用+defaultActionForKey:来获取

查找返回的结果:

  1. 返回NSNull, 停止查找
  2. 返回nil,继续往下查找
  3. 返回一个CAAnimation对象,来执行动画

NSNull也遵守CAAction协议:

// CALayer.h中
/** NSNull protocol conformance. **/
@interface NSNull (CAActionAdditions) <CAAction>
@end

如果我们自定义一个Layer,也可让自己定义的属性在被更改时产生隐式动画,只要重写- (nullable id<CAAction>)actionForKey:(NSString *)event或者+defaultActionForKey:,返回一个适当的CAAnimatin即可。

另外,隐式动画只存在于单独的Layer (stand alone layer),对于依附于UIViewLayer (backing layer),可动画属性的改变不会造成隐式动画。
这是因为UIViewCALayer的代理,当layer向代理询问是否有id<CAAction>时,UIView总是返回null

- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
    return [NSNull null];
}

保持动画后状态

保持动画完成后状态的两种方法:
一:设置removedOnCompletionNO,并设置fillModekCAFillModeForward
二:在addAnimation: 前将layer的属性置为动画后的属性。

方法二性能优于方法一,如果将已完成的动画保持在 layer 上时,会造成额外的开销,因为渲染器会去进行额外的绘画工作。

对于backing layer,在添加动画的前或后,将属性修改到最终状态都可以。

而对于stand alone layer,不能在添加显式动画后修改,因为修改属性会带来隐式动画,这样就会同时存在两个动画,在动画的渲染时,同一个keyPath对应多个动画,后添加的动画(隐式动画)值会覆盖前者,会造成动画不自然。

var myAnimation = CABasicAnimation(keyPath: "position.x")
myAnimation.fromValue = oldValue 
myAnimation.toValue = newValue
myAnimation.duration  = 1.0

// set layer's position to newValue before add explicit animations
layer.position.x = newValue.floatValue // now there is a new model value

layer.addAnimation(myAnimation, forKey: "move along X")

在添加动画前和添加动画后更新layer属性值的对比:

before-vs-after.gif

具体可以阅读:Multiple Animations

我们也可以在更新属性值时,使用CATransaction来禁用隐式动画,达到和backing layer一样的效果。

var myAnimation = CABasicAnimation(keyPath: "position.x")
myAnimation.fromValue = oldValue // still the current model value
// if you want to be explicit about the toValue you can set it here
myAnimation.duration  = 1.0

CATransaction.begin()
CATransaction.setDisableActions(true)
myLayer.position.x = newValue // now there is a new model value
CATransaction.commit()

myLayer.addAnimation(myAnimation, forKey: "move along X")

动画的重用

addAnimation:layer 会拷贝一份动画对象,之后动画对象再发生改变,将不会影响原图层的动画。

[firstLayer addAnimation: animation];
animation.beginTime = CACurrentMediaTime() + 0.5;
[secondLayer addAnimation: animation];

对动画开始时间的修改将只影响第二个图层,这可以让我们有效地重用动画。

动画的 additive 属性为YES 时,动画中属性的值会叠加到modelLayer 上来产生presentationLayer ,也就是在原图层属性的基础上加上动画的属性值,这样动画中可以不必考虑原图层的具体属性值。
例如一个左右移动的关键帧动画:

CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position.x";
animation.values = @[ @0, @10, @-10, @10, @0 ];
animation.duration = 0.4;
animation.additive = YES;
[layer addAnimation:animation forKey:@"shake"];

动画中的属性值,不需要带入layer 的属性去计算最左、最右和中间点的坐标,而是直接叠加在layer 的属性上。这样也有利于动画的重用。

rotationMode

使用path 的关键帧动画,可以设置rotationModekCAAnimationRotateAuto 让视图根据角度旋转。

rotationModekCAAnimationRotateAuto

rotationModenil

additive

一个键路径(key path)上有多个动画时,经常会让整体动画变得诡异莫测。因为在动画的渲染过程中,同一的键路径上添加的动画,后者的值会覆盖前者的。
而当这个动画的addtive属性为YES时,情况会有所改变,与上面说到的动画值的覆盖不同,一个addtive的动画,会将值在原动画的基础上叠加,layermodel也会及时更新到新的值。

在一个动画的执行过程中,我们可以在任意时间点加一个addtive的动画来改变整体的动画。这可以让我们的动画更加动态化。
比如在一个视图动画的过程中产生了用户交互,我们可以在交互发生时再增加一个addtive的动画来反馈出交互的效果。

多个addtive动画可以在同一键路径上,实现一些比较复杂的效果,比如下面的动画。

A repeating animation of one path (a circle) looping while following another path (a heart)

这样的效果如果使用其他方式实现,可能会比较复杂。而使用addtive动画,则可以轻松实现。

let followHeartShape = CAKeyframeAnimation(keyPath: "position")
followHeartShape.additive = true
followHeartShape.path     = heartPath
followHeartShape.duration = 5
followHeartShape.repeatCount     = HUGE
followHeartShape.calculationMode = "paced"

let circleAround = CAKeyframeAnimation(keyPath: "position")
circleAround.additive = true
circleAround.path     = circlePath
circleAround.duration = 0.275
circleAround.repeatCount     = HUGE
circleAround.calculationMode = "paced"

layer.addAnimation(followHeartShape, forKey: "follow a heart shape")
layer.addAnimation(circleAround,     forKey: "loop around")

CAMediaTimingFunction

CAMediaTimingFunction可以用来控制动画的节奏。
在代码中我们常使用[CAMediaTimingFunction functionWithName:]方法来使用一些系统封装好的CAMediaTimingFunction实例。其中有:

  1. linear:
  • kCAMediaTimingFunctionLinear
  1. easing:
  • kCAMediaTimingFunctionEaseIn
  • kCAMediaTimingFunctionEaseOut
  • kCAMediaTimingFunctionEaseInEaseOut
  • kCAMediaTimingFunctionDefault

也有一些第三方库(如RBBAnimation)提供了其他的easing效果方便使用。

此外, 我们还可以使用[CAMediaTimingFunction functionWithControlPoints:x1:y1:x2:y2]来创建自定义的timimgFunction,创建出来的函数是以贝塞尔曲线为模型,参数中提供两个控制点的坐标。但是这样创建出来的函数很难形象地看出曲线长什么样子,可以通过CAMediaTimingFunction playground网站,输出控制点得到最终的贝塞尔曲线:

或者使用他们提供的Mac AppTween-o-Matic, 也有类似的功能,还提供对应的Demo动画效果:

自定义缓冲函数

如果想要自定义缓冲函数对CAMediaTimingFunction进行丰富或实现它不能实现的效果,可以参考自定义缓冲函数。一些第三方也是以按其中的方法进行封装的。

参考资料:
Obj中国
卷首语
动画解释
Multiple Animations

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

推荐阅读更多精彩内容