[译]Uberworks

原文: Uberworks

作者: Marin Todorov

译者:kmyhy

本教程使用 Xcode 7.1 和 Swift 2。

这个月的主题又回到了这一系列文章的开始。事实上,我正在华盛顿参加 RWDevCon 大会,在会上我将推出 iOS Animations by Tutorials 和这一系列文章。我收到了一封来自 Uber 的主题为 Forget your keys 的邮件,它上面有一个烟火动画获得了我的喜爱。你可以在这篇 tweet 上看到这个动画:

这个动画于是就成为了我待办列表中的一项长达 8 个月之久,现在,是时候用 Core Animation 和 CAReplicatorLayer 来实现它了。

开始

我已经准备了一个开始项目,以节省你新建项目和创建 UI 和自动布局的工作。

下载 Uberworks-starter.zip ,解压缩,打开 Uberworks.xcodeproj。

这个项目包含了一个空的黑色的 view controller,以及 EasyAnimation 库。(关于 EasyAnimation 库你可以在 6 月份的教程中看到)。

要实现烟火效果,需要我们创建几个动画,在 ViewController.swift 中添加必须的代码。

开始来写代码吧!

创建尾焰

首先来创建烟花开始发射后的尾焰。

在 ViewController 中添加一个助手方法:

func animatedLineFrom(from: CGPoint, to: CGPoint) -> CAShapeLayer {
  let linePath = UIBezierPath()
  linePath.moveToPoint(from)
  linePath.addLineToPoint(to)

}

animatedLineFrom(_:to:) 会创建一个 CAShapeLayer,允许你添加到 layer 树中。这个方法也会在这个 layer 上创建和运行动画,这样你可以直接显示它就行了。

第一段代码中创建了一个贝塞尔路径,代表了你将在屏幕上绘制的形状。对于尾焰来说,我们只需要一条由参数指定的从起点到终点的直线就可以了。

现在,在 aminatedLineFrom(_:to:) 方法底部创建 CALayer:

let line = CAShapeLayer()
line.path = linePath.CGPath
line.lineCap = kCALineCapRound
line.strokeColor = UIColor.cyanColor().CGColor

return line

这段代码创建了一个 CALayer,用于绘制这条线,设置颜色为蓝绿色,然后返回 CALayer。

现在来试试它。在 viewDidLoad() 方法中加入:

view.layer.addSublayer(
    animatedLineFrom(CGPoint(x: 160, y: 550), to: view.center)
)

这段代码在屏幕上创建了一条青色的线条:

然后——我们对线条的起点和终点进行动画,创建出一种尾焰效果。回到animatedLineFrom(_:to:) 方法,在 return 之前加入:

line.strokeEnd = 0.0
UIView.animateWithDuration(1.0, delay: 0.0, options: [.CurveEaseOut], animations: {
    line.strokeEnd = 1.0
}, completion: nil)

首先将终点设置为起点(也就是线条将变成 0 长度)。然后以动画的方式将终点重设回路径的终点(也就是动画将从 0% 逐渐变成 100%)。

关于 strokeEnd 和 strokeBegin 的内容,请参考 iOS Animations by Tutorials 的第 15 章“笔触和路径动画”。

运行 app,看看效果:

然后给这个动画添加一个在短暂停留后消失的尾迹效果,添加第二个动画:

UIView.animateWithDuration(0.75, delay: 0.9, options: [.CurveEaseOut], animations: {
    line.strokeStart = 1.0
}, completion: nil)

这会将线段的起点朝终点移动直到消失:

很好——烟花动画的第一部分完成了!

你的第一支烟花

现在来实现最有趣的部分——焰火的绽放。

焰火的绽放需要用到一个 replicator 图层。如果你没用过 CAReplicatorLayer,建议你阅读 iOS Animations by Tutorials 第 16 章“复制动画”。

在 ViewController 中添加一个新的助手方法,用于创建一个添加到 view 树中的焰火。

func firework1(at atPoint: CGPoint) -> CAReplicatorLayer {
    let replicator = CAReplicatorLayer()
    replicator.position = atPoint

    return replicator
}

这个方法创建了一个空的 replicator layer 并返回它。我们将在这个方法的 return 之前添加动画代码。

首先创建一个线条路径,用于表示绽开后的焰火粒子(和你之前创建的线条路径一样)。在 firework1(at:) 方法中加入:

//line path
let f11linePath = UIBezierPath()
f11linePath.moveToPoint(CGPoint(x: 0, y: -10))
f11linePath.addLineToPoint(CGPoint(x: 0, y: -100))

f11linePath 是表示“第一种焰火的第一条线条路径”。剩下的代码和之前一样。

然后,再次像前面一样,用一个 CAShapeLayer 绘制这条线段:

//line 1
let f11line = CAShapeLayer()
f11line.path = f11linePath.CGPath
f11line.strokeColor = UIColor.cyanColor().CGColor
replicator.addSublayer(f11line)

创建了线条所在的图层,然后设置其颜色为青色,最后将它添加到复制器图层中。

回到 viewDidLoad() 方法,继续添加代码:

delay(seconds: 1.0, completion: {
  self.view.layer.addSublayer(self.firework1(at: self.view.center))
})

这将绘出焰火绽放之后的第一缕火焰:

然后,发挥 CAReplicatorLayer 的作用吧,在 firework1at(_:) 方法中追加:

replicator.instanceCount = 20
replicator.instanceTransform = CATransform3DMakeRotation(CGFloat(M_PI/10), 0, 0, 1)

这会复制你的绽放后的光束,将这些复制品排成环状:

猜到了吧?你的下一个动作就是和之前一样添加 strokeEnd 的动画。添加代码:

f11line.strokeEnd = 0
UIView.animateWithDuration(1.0, delay: 0.33, options: [.CurveEaseOut], animations: {
    f11line.strokeEnd = 1.0
}, completion: nil)

干得不错,来看看这个爆炸的效果吧:

现在——我们来添加另一种爆炸后的线型。我们将用另外一个形状图层添加到 replicator 图层中,以区别于前面的代码。

加入:

//line path 2
let f12linePath = UIBezierPath()
f12linePath.moveToPoint(CGPoint(x: 0, y: -25))
f12linePath.addLineToPoint(CGPoint(x: 0, y: -100))
f12linePath.applyTransform(CGAffineTransformMakeRotation(CGFloat(M_PI/20)))

我们和之前一样画了一条线,但这次稍微旋转了一个角度——防止它和第一条线重叠。

添加创建第二个形状图层的代码,然后将它添加到复制器图层中:

let f12line = CAShapeLayer()
f12line.path = f12linePath.CGPath
f12line.lineDashPattern = [20, 2]
f12line.strokeColor = UIColor.cyanColor().CGColor
replicator.addSublayer(f12line)

f12line.strokeEnd = 0

和之前一样,创建了这个线条的 CALayer,设置颜色为青色。这次你将线型设置为虚线——虚线的样式为:20 像素的短线,2个像素的间距(也就是,这个图层会先绘制 20 像素的短线,间隔 2 个像素,又绘制另外 20 像素的短线,依次类推)。

然后设置 strokeEnd 为 0,接下来就要进行动画了。添加:

UIView.animateWithDuration(1.0, delay: 0.0, options: [.CurveEaseOut], animations: {
  f12line.strokeEnd = 1.0
}, completion: nil)

基本的架子已经打起来了。接下来需要稍微加工一下,让它看起来更好看一些!

首先为它的消失增加一点动效。添加代码:

UIView.animateWithDuration(1.0, delay: 1.0, options: [.CurveEaseIn], animations: {
  f11line.strokeStart = 1.0
  f12line.strokeStart = 0.5
}, completion: nil)

和之前一样,你对 strokeStart 进行动画,让线段“消失”:

让我们将最后的变化再加一点动画——在最后一个动画块中加入:

f11line.transform = CATransform3DMakeRotation(CGFloat(M_PI_4), 0, 0, 1)
f12line.transform = CATransform3DMakeRotation(CGFloat(M_PI_4/2), 0, 0, 1)

replicator.opacity = 0.0

这段代码对两种线型的线段进行了一点旋转,然后让整个复制图层渐出,制造一种爆炸后原本就有的渐隐效果。

漂亮!这个烟花绽放的效果非常棒,和真实的效果非常接近。

如果你重用这段代码,别忘了动画结束之后,哪怕焰火和图层都看不见,但它们仍然还在图层树中存在,你必须在爆炸结束之后手动移除它们。

你的第二只烟花

现在,为了更好玩,我们将创建第二种类型的烟花,就和你在 Uber 视屏中看到的另一种烟火一样。

首先加一个助手方法,用于创建一个蓝绿色的点:

func animatedDot(withDistance delta: CGFloat, delay: Double) -> CALayer {
  let dot = CALayer()
  dot.backgroundColor = UIColor.cyanColor().CGColor
  dot.frame = CGRect(x: 0, y: -10, width: 1, height: 1)

  return dot
}

和之前的助手方法一样,创建一个图层,设置它的 frame、颜色然后返回它。这两个参数现在没有用到,暂不讨论。

接下来是第二个助手方法(和之前干的非常类似):

func firework2(at atPoint: CGPoint) -> CAReplicatorLayer {
  let replicator = CAReplicatorLayer()
  replicator.position = atPoint

  replicator.instanceCount = 40
  replicator.instanceTransform = CATransform3DMakeRotation(CGFloat(M_PI/20), 0, 0, 1)

  return replicator
}

firework2at(at:) 方法创建了一个空的 CAReplicatorLayer,指定它的位置,然后创建 40 个实例围成环形。

现在尝试着在 replicator layer 中添加一个绿色小店——两个参数都使用 0,因为 animatedDot(_:delay:) 方法还没有用到这两个参数。在 return 之前加入:

replicator.addSublayer(
  animatedDot(withDistance: 0, delay: 0)
)

为了让这些代码生效,需要回到 viewDidLoad() 方法,将这句:

self.view.layer.addSublayer(self.firework1(at: self.view.center))

替换成:

self.view.layer.addSublayer(self.firework2(at: self.view.center))

二者唯一的不同是方法名中的“1”变成了“2”。你也可以只修改这个数字就可以了。

这会让第二种焰火呈现:

现在,我们可以将复制动画中的点偏移一小点。找到 animatedDot(_:delay:) 方法,在 return 之前添加:

UIView.animateAndChainWithDuration(1.0, delay: delay, options: [.CurveEaseOut], animations: {
  dot.transform = CATransform3DMakeTranslation(0, -delta, 0)
}, completion: nil)

这个动画块中修改了圆点图层的 transform 属性,将它的 Y 坐标移动了 delta 个像素。为了让这个起作用,你必须在调用 animatedDot(_:delay:) 方法时指定 delta 参数的值。在 firework2(_:) 方法中,找到animatedDot(withDistance: 0, delay: 0) 这句,将第一个参数修改为 50:

animatedDot(withDistance: 50, delay: 0)

现在可以来欣赏下这个简单而又漂亮的爆炸效果了:

好了——就快完成了。为这个点添加更多的动画吧。

在 animatedDot(_:delay:) 中添加更多的动画到动画序列中——你的动画代码看起来应该是这个样子:

UIView.animateAndChainWithDuration(1.0, delay: delay, options: [.CurveEaseOut], animations: {
  dot.transform = CATransform3DMakeTranslation(0, -delta, 0)
}, completion: nil)
.animateWithDuration(2.0, animations: {
  dot.transform = CATransform3DConcat(dot.transform, CATransform3DMakeRotation(CGFloat(M_PI_4), 0, 0, 1))
})
.animateWithDuration(1.0, animations: {
  dot.transform = CATransform3DConcat(dot.transform, CATransform3DMakeTranslation(0, -delta, 0))
  dot.opacity = 0.1
})

动画序列的第一部分是将圆点进行垂直移动。第二部分给圆点的 transform 属性加一小点旋转角度——那将使它们在爆炸之后所有的点旋转。最后让圆点在飞离的同时渐隐。需要执行的代码都很少:]

看起来很炫的效果!

好了——现在是最后一步:在爆炸中添加更多的点,看看要如何复制出来!

回到 firework2At(_:) 方法找到这段代码:

replicator.addSublayer(
  animatedDot(withDistance: 50, delay: 0)
)

用一个循环替换这段代码,创建 10 个点,让它们加一点延迟和偏移距离,创建更复杂的复制动画。将上段代码替换为:

for i in 1...10 {
  replicator.addSublayer(
    animatedDot(withDistance: CGFloat(i*10), delay: 1/Double(i))
  )
}

这将创建 10 个点并将它们添加到复制器中。每个点都会有不同的偏移距离以及不同的时间延迟。不用我多做解释,直接运行 app 看看:

今天就到此结束了!我希望你喜欢这个烟花动画并将它们用在你的 app 里:]

我截取了一段视频,显示了你今天完成的崭新的示例效果。

网页中嵌入的视频有点模糊——如果你想看到色彩逼真的顺滑的动画效果,请在 Vimeo 上打开视频并打开 HD。

接下来做什么?

如果你想了解更多关于笔触动画、复制动画和 EasyAnimation ——这些内容都包含在了 iOS Animation by Tutorials 的章节中。这本书的第二版包含了 3 个全新章节,内容也进行了相应的升级。

如果你购买了第一版的 PDF——别忘了进入你的 raywenderlich.com 的 profile 中,去下载免费升级的第二版!

如果你准备基于本教程的内容编写一些好玩的东东,请回复这封邮件或者 twitter 我 @icanzilb。

责编内容来自:kmyhy专栏 (源链) | 更多关于

阅读提示:酷辣虫无法对本内容的真实性提供任何保证,请自行验证并承担相关的风险与后果!
本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 移动开发 » [译]Uberworks

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录