第 17 章:圆环菜单的动画效果
原文链接
作者:C4 开源项目
译者:Crystal Sun
全部章节请关注此文集C4教程翻译
校对后的内容请看这里
尽管我已经把这章从上一章节里分出来了,我们还是要在 MenuRings.swift
文件里进行开发。运行应用,确保你看到的应用效果如下图:
如果不是这个效果,回到前一章节里,确保所有的变量设置正确,特别是菜单里的每条线、每个内环的变量值。
厚指环的动画效果
对每个组件我们都会设置自己的进出效果和转换效果。对于厚指环的动画效果来说,我们先在类里添加下面两个变量:
var thickRingOut : ViewAnimation?
var thickRingIn : ViewAnimation?
现在,创建一个方法,写代码:
func createThickRingAnimations() {
thickRingOut = ViewAnimation(duration: 0.5) {
self.thickRing?.frame = self.thickRingFrames[1]
}
thickRingOut?.curve = .EaseOut
thickRingIn = ViewAnimation(duration: 0.5) {
self.thickRing?.frame = self.thickRingFrames[0]
}
thickRingIn?.curve = .EaseOut
}
这个方法设置了两个动画,第一个 out 设置的是厚指环的新 frame,告诉 shape 更新自己的路径,强迫 shape 重画路径直到符合 frame。第二个则与 out 相反。
让我们测试一下。
创建下面这两个方法:
func animOut() {
delay(1.0) {
self.thickRingOut?.animate()
}
delay(2.0) {
self.animIn()
}
}
func animIn() {
delay(1.0) {
self.thickRingIn?.animate()
}
delay(2.0) {
self.animOut()
}
}
接着在 setup 里输入下列代码:
self.createThickRingAnimations()
animOut()
得到了这样的效果:
薄指环的动画效果(出)
先给我们的薄指环创建动画效果,和之前的步骤有些不同,因为我们需要协调五条线,之前只是一条线,所以我们使用循环体来创建,存储到数组中。
在类里增加下面两个变量:
var thinRingsOut : ViewAnimationSequence?
接着,增加下列方法:
func createThinRingsOutAnimations() {
var animationArray = [ViewAnimation]()
for i in 0..<self.thinRings.count-1 {
let anim = ViewAnimation(duration: 0.075 + Double(i) * 0.01) {
let circle = self.thinRings[i]
//每个动画都大于第一个
if (i > 0) {
//把指环的透明度调整到 1.0
ViewAnimation(duration: 0.0375) {
circle.opacity = 1.0
}.animate()
}
circle.frame = self.thinRingFrames[i+1]
}
anim.curve = .EaseOut
animationArray.append(anim)
}
thinRingsOut = ViewAnimationSequence(animations: animationArray)
}
这个创建了一系列动画变量,基在很多章节之前设置的目标 frame 的值来填充动画。
这个方法创建了一个动画数组,然后运行循环:
- 抓住每个瘦指环,从小到大
- 创建动画,持续时间基于当前的 index
(0.075 + Double(i) + 0.01)
- 检查每一行的 index 是否大于零,比如,不是内圆
- 对于任何一个非内圆,创建一个不透明的动画,执行后会立即消失
- 抓住当前目标的 frame,也就是当前 index + 1
- 设置当前圆环的 frame,调用
updatePath
- 设置动画的曲线为
.EaseOut
- 把动画添加到数组里
循环结束后,创建完成 thinRingsOut
序列。
薄指环动画效果(入)
回来的动画和出的动画非常相似,仅仅有几处地方不同。
在类里增加下列变量:
var thinRingsIn : ViewAnimationSequence?
增加下列方法:
func createThinRingsInAnimations() {
var animationArray = [ViewAnimation]()
for i in 1...self.thinRings.count {
let anim = ViewAnimation(duration: 0.075 + Double(i) * 0.01, animations: { () -> Void in
let circle = self.thinRings[self.thinRings.count - i]
if self.thinRings.count - i > 0 {
ViewAnimation(duration: 0.0375) {
circle.opacity = 0.0
}.animate()
}
circle.frame = self.thinRingFrames[self.thinRings.count - i]
})
anim.curve = .EaseOut
animationArray.append(anim)
}
thinRingsIn = ViewAnimationSequence(animations: animationArray)
}
不同之处在于:
- 这里的数学计算表示循环体和出的效果正好相反:
self.thinRings.count - 1
- 透明度调整为
0.0
- 由于我们总是想获取数组“之前”的数字(比如
-1
),所以我们的循环执行如下:for i in 1...self.thinRings.count
虚线环的动画效果
要完成这些动画,还需要创建两个新的动画变量:
var revealDashedRings : ViewAnimation?
var hideDashedRings : ViewAnimation?
增加下列方法:
func createDashedRingAnimations() {
revealDashedRings = ViewAnimation(duration: 0.25) {
self.dashedRings[0].lineWidth = 4
self.dashedRings[1].lineWidth = 12
}
revealDashedRings?.curve = .EaseOut
hideDashedRings = ViewAnimation(duration: 0.25) {
self.dashedRings[0].lineWidth = 0
self.dashedRings[1].lineWidth = 0
}
hideDashedRings?.curve = .EaseOut
}
查看一下效果
想要看出、入效果,需要修改 animOut
和 animIn
方法,如下:
func animOut() {
delay(1.0) {
self.thickRingOut?.animate()
self.thinRingsOut?.animate()
self.revealDashedRings?.animate()
}
delay(2.0) {
self.animIn()
}
}
func animIn() {
delay(1.0) {
self.thickRingIn?.animate()
self.thinRingsIn?.animate()
self.hideDashedRings?.animate()
}
delay(2.0) {
self.animOut()
}
}
在 setup() 里调用 animOut():
createThinRingsOutAnimations()
createThinRingsInAnimations()
createDashedRingAnimations()
运行,效果如下图:
估计现在你已经注意到了,指环和竖线的调速有些问题,不用担心,在最后把所有的组件组合到一起后,会处理这个问题的。
快要到达终点了。
分割线
分割线的动画效果比较简单,除了 Jake 的要求,它们出现的顺序是随机的。翻译一下就是:每次线出现的动画和顺序都是要随机的,要和之前的有所不同。
我是这样解决这个问题的:
func revealHideDividingLines(target: Double) {
var indices = [0,1,2,3,4,5,6,7,8,9,10,11]
for i in 0...11 {
delay(0.05*Double(i)) {
let randomIndex = random(below: indices.count)
let index = indices[randomIndex]
ViewAnimation(duration: 0.1) {
self.menuDividingLines[index].strokeEnd = target
}.animate()
indices.removeAtIndex(randomIndex)
}
}
}
把这些添加到类里。
这个方法做了下面这些事情:
- 获取一个目标(应该是
0.0
或者1.0
) - 创建 index 数组
- 运行循环,执行 12 次
- 每次遍历都从
接着,测试一下效果:
func animOut() {
delay(1.0) {
self.thickRingOut?.animate()
self.thinRingsOut?.animate()
}
delay(1.5) {
self.revealHideDividingLines(1.0)
}
delay(2.5) {
self.animIn()
}
}
func animIn() {
delay(0.25) {
self.revealHideDividingLines(0.0)
}
delay(1.0) {
self.thickRingIn?.animate()
self.thinRingsIn?.animate()
}
delay(2.0) {
self.animOut()
}
}
效果如下:
打扫房子
现在,不在调用 self.animOUt()
方法,删掉两个动画方法。
接着创建两个新方法:
func createRingsLines() {
createThickRing()
createThinRings()
createDashedRings()
createMenuDividingLines()
}
func createRingsLinesAnimations() {
createThickRingAnimations()
createThinRingsOutAnimations()
createThinRingsInAnimations()
createDashedRingAnimations()
}
更新 setup
方法如下:
public override func setup() {
canvas.backgroundColor = clear
canvas.frame = Rect(0,0,80,80)
createRingsLines()
createRingsLinesAnimations()
}
最后,在类里再添加一个变量:
var menuIsVisible = false
这最后一个变量我们在之后的章节中会用到。
MenuRings.swift 源文件。
本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权。