Layer 上的 Keyframe 动画与 UIView 上的关键帧动画有些不同。UIView 关键帧动画是将独立的简单动画组合在一起的一种简单方法。它们可以使不同的视图和属性产生动画效果,动画可以重叠,或者中间有空隙。
相反,CAKeyframeAnimation 允许我们对给定 Layer 上的单个属性进行动画处理。我们可以定义动画的不同关键点,但是动画不能有任何间隙或重叠。
keyframe animations
通过使用 fromValue 和 toValue,Core Animation 在指定的持续时间内逐步修改这些值之间的特定层属性。
比如,当旋转一个 layer 45°、-45°(或π/ 4和-π/ 4),我们只需要指定这两个值,层会渲染 π/ 4 和 -π/ 4 之间的所有中间值来完成动画。
CAKeyframeAnimation 使用 array of values 来动画,而不是由 fromValue 到 toValue 那样动画。array 中的值的是动画的关键值。除此,我们还需要提供动画应该到达每个值的关键点的时间。
上面的动画,layer 从 45° 旋转到 -45° ,但这次有两个不同的阶段:首先,前三分之二时间里它从 45° 旋转到 22°,剩下的时间里,它从 22° 旋转到 -45°。
Animating struct values
Struct instances 是 Swift 中的一等公民,在语法上,处理类和结构体之间没有什么区别。
然而,Core Animation 是构建在 C 上的 Objective-C 框架,这意味着结构体的处理方式非常不同。Objective-C api 喜欢处理对象,所以结构体需要一些特殊的处理。
这就是为什么将诸如 color 或 number 的 layer 属性进行动画是相对容易的,但是对诸如 CGPoint 这样的结构体属性进行动画并不那么容易的原因。
CALayer 的许多可动画属性都具有 struct 值,包括 CGPoint 类型的 position、CATransform3D 类型的 transform 以及 CGRect 类型的 bounds 。为了帮助管理,Cocoa 包含 NSValue 类,它可以将一个 struct 值 “装箱” 或 “包装” 成为对象。
NSValue 附带了许多方便的初始化方法,我们可以将它们用于需要装箱的每个结构体,例如如下初始化方法:
init(cgPoint: CGPoint)
init(cgSize: CGSize)
init(cgRect rect: CGRect)
init(caTransform3D: CATransform3D)
“boxes in” or “wraps” “装箱” 或 “包装”使用示例:
let move = CABasicAnimation(keyPath: "position")
move.duration = 1.0
move.fromValue = NSValue(cgPoint: CGPoint(x: 100.0, y: 100.0))
move.toValue = NSValue(cgPoint: CGPoint(x: 200.0, y: 200.0))
Intermediate keyframe animations
let balloon = CALayer()
balloon.contents = UIImage(named: "balloon")!.cgImage
balloon.frame = CGRect(x: -50.0, y: 0.0, width:50.0, height: 65.0) view.layer.insertSublayer(balloon, below: username.layer)
如果我们想要在屏幕上显示一个图像,但不需要使用 UIView 的一些特性(比如自动布局约束、附加手势识别器等等),那么我们可以使用一个 CALayer 添加图像,如上面的代码示例。
flight.values = [
CGPoint(x: -50.0, y: 0.0),
CGPoint(x: view.frame.width + 50.0, y: 160.0),
CGPoint(x: -50.0, y: loginButton.center.y)
].map { NSValue(cgPoint: $0) } // 使用map将一个点数组巧妙地转换成一个以nsvalue形式框起来的点数组
flight.keyTimes = [0.0, 0.5, 1.0]
使用 map 将一个 CGPoint 数组巧妙地转换成一个以 NSValue 形式框起来的点数组。
动画最后效果: