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
协议。
这个方法的查找顺序是:
- 调用代理(如果有)的
-actionForLayer:forKey:
方法 - 从
layer
的actions
字典中查找 - 从
style
字典中查找actions
字典来获取 - 调用
+defaultActionForKey:
来获取
查找返回的结果:
- 返回
NSNull
, 停止查找 - 返回nil,继续往下查找
- 返回一个
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),对于依附于UIView
的Layer
(backing layer),可动画属性的改变不会造成隐式动画。
这是因为UIView
是CALayer
的代理,当layer
向代理询问是否有id<CAAction>
时,UIView
总是返回null
。
- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
return [NSNull null];
}
保持动画后状态
保持动画完成后状态的两种方法:
一:设置removedOnCompletion
为NO
,并设置fillMode
为kCAFillModeForward
。
二:在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
属性值的对比:
具体可以阅读: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
的关键帧动画,可以设置rotationMode
为kCAAnimationRotateAuto
让视图根据角度旋转。
rotationMode
为kCAAnimationRotateAuto
rotationMode
为nil
additive
一个键路径(key path)上有多个动画时,经常会让整体动画变得诡异莫测。因为在动画的渲染过程中,同一的键路径上添加的动画,后者的值会覆盖前者的。
而当这个动画的addtive
属性为YES
时,情况会有所改变,与上面说到的动画值的覆盖不同,一个addtive
的动画,会将值在原动画的基础上叠加,layer
的model
也会及时更新到新的值。
在一个动画的执行过程中,我们可以在任意时间点加一个addtive
的动画来改变整体的动画。这可以让我们的动画更加动态化。
比如在一个视图动画的过程中产生了用户交互,我们可以在交互发生时再增加一个addtive
的动画来反馈出交互的效果。
多个addtive
动画可以在同一键路径上,实现一些比较复杂的效果,比如下面的动画。
这样的效果如果使用其他方式实现,可能会比较复杂。而使用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
实例。其中有:
- linear:
kCAMediaTimingFunctionLinear
- 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