原文:Creating animations with CAReplicatorLayer
作者:Marin Todorov
译者:kmyhy
本教程针对 Xcode 7/Swift 2 或更高版本。
CAReplicatorLayer 绝对是我最爱的 CALayer,因此我很愿意向你介绍如何用它来创建动画。
在本教程中,我将向你展示用 CAReplicatorLayer 创建 3 种不同的动画。
首先模拟一个类似 iOS 音乐 app 中的音量条动画(在“Pyramid Song”右边有一个会动的柱状图):
然后创建一个自定义的进度指示器。最后以一种非同一般的效果渲染出 ranywenderlich.com 的网站 logo。
1. 基本的 replicator 动画
CAReplicatorLayer 是一个容器图层——你向它上面添加内容,然后 CAReplicatorLayer 会重复这些内容。如果你放入的是单个形状——CAReplicatorLayer 会在屏幕上显示多个形状。
有意思的是,你可以提前告诉 replicator 在这个复制的内容之间要偏移多少,alpha 是多少以及一个拷贝和另一个拷贝之间颜色如何改变。这允许你创建出非常炫的效果和动画。
本教程的第一部分是模拟 iOS Music app 的动画:
这个动画会画一个红色的会上下动的柱子,复制两个这样的动画进,然后对形状和时间进行一点偏移。让我们开始吧!
新建 Single View Application 项目。打开 ViewController.swift。
在 viewDidLoad() 中添加:
animation1()
func animation1() { }
在 animation1 方法中创建 CAReplicatorLayer:
let r = CAReplicatorLayer()
r.bounds = CGRect(x: 0.0,y: 0.0,width: 60.0,height: 60.0)
r.position = view.center
r.backgroundColor = UIColor.lightGrayColor().CGColor
view.layer.addSublayer(r)
你创建了一个 CAReplicatorLayer 对象,设置它的 bounds 和位置。为了能够看得清它的位置,可以为它指定一个浅灰色的背景色,然后将它添加到 view controller 的 view。
运行 app,你会看到一个灰色方块:
首先创建第一个 bar(也就是最初的 bar),在 animation1 中添加:
let bar = CALayer()
bar.bounds = CGRect(x: 0.0,width: 8.0,height: 40.0)
bar.position = CGPoint(x: 10.0,y: 75.0)
bar.cornerRadius = 2.0
bar.backgroundColor = UIColor.redColor().CGColor
r.addSublayer(bar)
这段代码创建了一个红色的圆角矩形,将它放到 replicator layer 的左边。运行 app 看看效果:
这个 bar 显示到了 replicator layer 的外边了,因为你将上下移动它。在动画一开始它将位于 replicator layer 的边界之下——因此它看起来是“关闭”的样子。
let move = CABasicAnimation(keyPath: "position.y")
move.toValue = bar.position.y - 35.0
move.duration = 0.5
move.autoreverses = true
move.repeatCount = Float.infinity
bar.addAnimation(move,forKey: nil)
这将让 bar 反复地上移下移… 这是一个了不起的开始,哪怕目前它看起来是那么的不起眼,但实际上你已经为最终的动画做好了准备!
接下来看看 replicator 的威力!在你的代码中添加:
r.instanceCount = 3
这会告诉 replicator 你将在屏幕内容中创建 3 个拷贝——包括你最初的那个在内。如果你运行 app,你不会看到任何区别,因为 3 个拷贝是在同一个位置以同时同步的方式显示的。要将每个拷贝的位置向右添加一点偏移,只需要:
r.instanceTransform = CATransform3DMakeTranslation(20.0,0.0,0.0)
这里你告诉 replicator layer 每个拷贝要应用的矩形变换。你将 instanceTransform 设置为每个拷贝偏移 20 像素,这样当运行 app 时就会看到 3 个 bar 一个挨着一个排列了:
啊!你原始的 bar 和另外两个拷贝都会上下移动……很好!最后一个步骤是给每个 bar 一个延时,以便它们不会一起动。最后加一句代码:
r.instanceDelay = 0.33
instanceDelay 是 replicator 在渲染每个拷贝的时间差。应用到原始 bar 的动画和第二个拷贝的动画之间有 0.33 秒的延迟,而它和第 3 个 bar 的延迟为 0.66。
运行 app,测试一下结果——你会看到这些 bar 就像原本的动画一样跳动着。
最后还有两个地方:
- 要想只显示 bar 和 replicator layer 相交的上部分,请用:.masksToBounds = true;
- 找到设置 replicator layer 的背景色的一行,删掉它。你不再需要灰色背景了。
最终动画效果如下:
如果你想尝试做些改变,可以修改 instanceCount、instanceTransform 和 instanceDelay。很酷吧?
2. 进度指示器
让我们来看一下更复杂的 replicator 动画!将 viewDidLoad() 中的 animation1() 改为:
animation2()
func animation2() { }
在这部分你将创建一个进度指示器。为了好玩,你将创建一个比内置 iOS activity view 更复杂的动画。
首先在 animation2() 方法中,添加一个 replicator layer 到 view controller 的 view 中:
let r = CAReplicatorLayer()
r.bounds = CGRect(x: 0.0,width: 200.0,height: 200.0)
r.cornerRadius = 10.0
r.backgroundColor = UIColor(white: 0.0,alpha: 0.75).CGColor
r.position = view.center
view.layer.addSublayer(r)
和之前一样,创建一个灰色背景的空的 replicator layer。这次你将保留背景色已模仿 HUD activity。
然后绘制一个白色的矩形:
let dot = CALayer()
dot.bounds = CGRect(x: 0.0,width: 14.0,height: 14.0)
dot.position = CGPoint(x: 100.0,y: 40.0)
dot.backgroundColor = UIColor(white: 0.8,alpha: 1.0).CGColor
dot.borderColor = UIColor(white: 1.0,alpha: 1.0).CGColor
dot.borderWidth = 1.0
dot.cornerRadius = 2.0
r.addSublayer(dot)
创建了一个 14x14 的矩形,圆角半径 2 像素。最后将 dot 层添加到 replicator 中。运行 app 是这个样子:
然后设置 replicator 让它绘制 15 个 dot 并每个旋转 2π/15 度(也就是这个角度乘以 15 构成一个整圆):
let nrDots: Int = 15
r.instanceCount = nrDots
let angle = CGFloat(2*M_PI) / CGFloat(nrDots)
r.instanceTransform = CATransform3DMakeRotation(angle,0.0,1.0)
你将 instanceCount 设置为 15,然后设置一个旋转矩阵变换,旋转 2π/15 弧度。
注意:如果你觉得有点难,请参考 iOS Animations by Tutorials 的 8-11 章关于图层动画,以及 12-14 章关于特殊图层。
运行 app,开始欣赏:
你可以将 nrDots 修改为 10、25 或别的值,简单体验一下你的小菊花。replicator 会忠实地为你计算这形状并显示出来:
现在让这些 dot 每 1.5 秒就进行缩小。创建一个缩放动画,将它应用到第一个 dot 上:
let duration: CFTimeInterval = 1.5
let shrink = CABasicAnimation(keyPath: "transform.scale")
shrink.fromValue = 1.0
shrink.toValue = 0.1
shrink.duration = duration
shrink.repeatCount = Float.infinity
dot.addAnimation(shrink,forKey: nil)
这会导致一种催眠动画,所有的 dot 都会同步地一遍遍动起来并消失(请自己小心,不要长时间看这个动画)。
希望你还记得要让这个动画动起来,这只需要给每个拷贝加一个延时:
r.instanceDelay = duration/Double(nrDots)
这会让你的动画完美地旋转。只不过动画第一遍循环时有一点奇怪的地方——所有的 dot 一开始都是显示的,只有在第一次循环之后它们才会消失。要解决这个问题,你需要将最初的 dot 在动画开始之前缩小,在 animation2() 最后一句添加:
dot.transform = CATransform3DMakeScale(0.01,0.01,0.01)
这样动画就平滑了:
哇,这个动画的创建比想象的简单吧?如果你熟悉了上面的基本进度指示器代码之后,你可以创建轻易创建出各种效果,试试吧!
3. Follow the leader
最后一个最精彩的动画是 Follow the leader。这个动画中你将让你的原始 layer 沿某条路径运动,让它的复制品跟随着它并试图跟上它。
将 viewDidLoad() 方法中的 animation2() 替换为:
animation3()
func animation3() { }
对于这个动画,你需要的方法不止一个。我会用 PaintCode 快速创建一个贝塞尔路径,然后将这个路径用于动画。在 ViewController 中添加这个方法:
func rw() -> CGPath {
//// Bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(31.5,71.5))
bezierPath.addLineToPoint(CGPointMake(31.5,23.5))
bezierPath.addCurveToPoint(CGPointMake(58.5,38.5),controlPoint1: CGPointMake(31.5,23.5),controlPoint2: CGPointMake(62.46,18.69))
bezierPath.addCurveToPoint(CGPointMake(53.5,45.5),controlPoint1: CGPointMake(57.5,43.5),controlPoint2: CGPointMake(53.5,45.5))
bezierPath.addLineToPoint(CGPointMake(43.5,48.5))
bezierPath.addLineToPoint(CGPointMake(53.5,66.5))
bezierPath.addLineToPoint(CGPointMake(62.5,51.5))
bezierPath.addLineToPoint(CGPointMake(70.5,66.5))
bezierPath.addLineToPoint(CGPointMake(86.5,23.5))
bezierPath.addLineToPoint(CGPointMake(86.5,78.5))
bezierPath.addLineToPoint(CGPointMake(31.5,71.5))
bezierPath.closePath()
var t = CGAffineTransformMakeScale(3.0,3.0)
return CGPathCreateCopyByTransformingPath(bezierPath.CGPath,&t)!
}
这个方法创建了一个贝塞尔路径并返回一个 CGPath——你将用这个 CGPath 创建一个关键帧动画。
在 animation3() 中添加:
let r = CAReplicatorLayer()
r.bounds = view.bounds
r.backgroundColor = UIColor(white: 0.0,alpha: 0.75).CGColor
r.position = view.center
view.layer.addSublayer(r)
这次你创建了一个和 view controller 的 view 一样大的空的 replicator layer,并将它添加到屏幕上。和之前一样,你首先需要添加第一个 layer 给 replicator 使用:
let dot = CALayer()
dot.bounds = CGRect(x: 0.0,width: 10.0,height: 10.0)
dot.backgroundColor = UIColor(white: 0.8,alpha: 1.0).CGColor
dot.borderWidth = 1.0
dot.cornerRadius = 5.0
dot.shouldRasterize = true
dot.rasterizationScale = UIScreen.mainScreen().scale
r.addSublayer(dot)
你创建了一个灰色小矩形,圆角半径是宽度的一半,这样它就变成了一个圆形。将这个圆加到 replicator 里。如果运行 app,你会看到在屏幕左上角有一个小圆。
让这个圆点沿路径移动:
let move = CAKeyframeAnimation(keyPath: "position")
move.path = rw()
move.repeatCount = Float.infinity
move.duration = 4.0
dot.addAnimation(move,forKey: nil)
这个动画让圆点沿 rw() 方法产生的路径移动。动画时长为 4 秒,无限循环。
注意:如果你想了解更多关于 layer 用路径进行关键帧动画的内容,请阅读 iOS Animations by Tutorials 第二版的第 15 章“笔触和路径动画”。
运行 app,你将看到圆点疯狂移动但却无法认出它描绘的路径是什么。
让我们将事情变得更明显一点,让 replicator layer 真正发挥作用。添加代码:
r.instanceCount = 20
r.instanceDelay = 0.1
这会添加 19 个拷贝,让他们跟随第一个圆点运动:
好棒!这个动画看起来更像你最爱的 raywenderlich.com 的 logo。
让我们给圆点加一点绿色,模拟网站的颜色。添加这句给 replicator 的内容上色:
r.instanceColor = UIColor(red: 0.0,green: 1.0,blue: 0.0,alpha: 1.0).CGColor
设置 instanceColor 会在原始内容的颜色上乘以你指定的颜色。这里,我们乘以浅绿色,这样你运行 app 之后你会看到一个很绿的动画:)))
有趣的一点是,你还可以让每个拷贝的 instanceColor 不同。这其实很简单,你可能想让绿色随着每个圆点越来越暗。在最后加上这句:
r.instanceGreenOffset = -0.03
这将使 replicator 将每个圆点的绿色部分递减 0.03。最后的动画效果如下:
我听到有人说:打开一个新 Xcode 项目,开始克隆一个贪吃蛇游戏?:]
这是本月教程中的最后一个动画!如果你认真阅读苹果文档,你会发现 CAReplicatorLayer 的内容很多。在本教程中未涉及的一个主题就是 CAReplicatorLayer 类制自己的动画属性(而不是 content 图层的动画属性)——那完全是另外一个主题了!
如果你用 CAReplicatorLayer 创建了某些漂亮的动画,别忘了 twitt 我 @icanzilb 给我欣赏一下。本文到此结束,别忘了阅读下面的链接!