原文链接
作者:C4 开源项目
译者:Crystal Sun
全部章节请关注此文集C4教程翻译
校对后的内容请看这里
接下来,想把图标添加到菜单里,然后实现动画效果。图标的设计理念非常直接:一开始像是一个点,慢慢变大,移动到最终的位置,出现全部的形状,如下图:
1. 定位
这部分有技巧的地方不是动画和移动效果,这些都简单,而是每个姓朱的位置,这样才能一开始是个点,慢慢转变成全部的形状。有一些方法可以实现这种效果:
- 创建一个点和一个形状(shape),一直隐藏形状知道点移动到自己的位置,隐藏点,显示形状。
- 创建一个点,让这个点成为形状的一部分,移动点到某个位置,然后把点转变成完全的形状。
- 有一个点,但不是真的点,只是看起来像是一个点,设置形状的
strokeEnd
在右边,形状的lineCap
是.Round
。
我们使用的是第三种方法 strokeEnd
来管理图标的动画。然而,问题也出现了,就是每个形状的“初始”点各不同 —— 我们需要知道每个形状,在点状态下或者在全部出现状态下的偏移量
下面是摩羯座的图标,开始点已经高亮标注出了:
开始点的位置是 {30.0,12.2}
,对应的 frame 是 {0.750,0.387}
。
创建一个“点”的效果,我们只需要给每个图标设置:
shape.strokeEnd = 0.001
shape.lineCap = .Round
如果我们用图标的中心点定位,结束和开始状态下的菜单看起来会是下图这样:
如果我们使用第一个点作为形状的 anchorPoint
,我们会得到下面这样的效果:
我们的想法是,在结束状态下使用 anchorPoint
,另外一个状态使用 center
。不过,如果我们不停的转换位置,实现起来会很复杂。所以我们必须使用 anchorPoint
计算实际的 center
,使用这个 center
在不同的状态下来换转换。
开始行动吧。
2. 获取图标
第一步就是从符号库里获取图标,更新图标的锚点。
打开 MenuIcons.swift
文件,在类里添加下列代码:
func taurus() -> Shape {
let shape = AstrologicalSignProvider.sharedInstance.taurus().shape
shape.anchorPoint = Point()
return shape
}
func aries() -> Shape {
let shape = AstrologicalSignProvider.sharedInstance.aries().shape
shape.anchorPoint = Point(0.0777,0.536)
return shape
}
func gemini() -> Shape {
let shape = AstrologicalSignProvider.sharedInstance.gemini().shape
shape.anchorPoint = Point(0.996,0.0)
return shape
}
func cancer() -> Shape {
let shape = AstrologicalSignProvider.sharedInstance.cancer().shape
shape.anchorPoint = Point(0.0,0.275)
return shape
}
func leo() -> Shape {
let shape = AstrologicalSignProvider.sharedInstance.leo().shape
shape.anchorPoint = Point(0.379,0.636)
return shape
}
func virgo() -> Shape {
let shape = AstrologicalSignProvider.sharedInstance.virgo().shape
shape.anchorPoint = Point(0.750,0.387)
return shape
}
func libra() -> Shape {
let shape = AstrologicalSignProvider.sharedInstance.libra().shape
shape.anchorPoint = Point(1.00,0.559)
return shape
}
func pisces() -> Shape {
let shape = AstrologicalSignProvider.sharedInstance.pisces().shape
shape.anchorPoint = Point(0.099,0.004)
return shape
}
func aquarius() -> Shape {
let shape = AstrologicalSignProvider.sharedInstance.aquarius().shape
shape.anchorPoint = Point(0.0,0.263)
return shape
}
func sagittarius() -> Shape {
let shape = AstrologicalSignProvider.sharedInstance.sagittarius().shape
shape.anchorPoint = Point(1.0,0.349)
return shape
}
func capricorn() -> Shape {
let shape = AstrologicalSignProvider.sharedInstance.capricorn().shape
shape.anchorPoint = Point(0.288,0.663)
return shape
}
func scorpio() -> Shape {
let shape = AstrologicalSignProvider.sharedInstance.scorpio().shape
shape.anchorPoint = Point(0.255,0.775)
return shape
}
每个方法都从符号库里获取了图标,设置锚点为图标的开始点。我们不需要知道符号的其他信息了(比如:大/小 点和线等等)。所以我们只需返回修改过锚点的形状。
接下来,我们还想能存储使用的符号的副本,之后我们会对这些符号进行操作。所以,创建一个形状词典变量来存储符号和方法,这些方法会给符号创建格子的风格:
var signIcons : [String:Shape]!
接着,把下列方法添加到类里:
func createSignIcons() {
signIcons = [String:Shape]()
signIcons["aries"] = aries()
signIcons["taurus"] = taurus()
signIcons["gemini"] = gemini()
signIcons["cancer"] = cancer()
signIcons["leo"] = leo()
signIcons["virgo"] = virgo()
signIcons["libra"] = libra()
signIcons["scorpio"] = scorpio()
signIcons["sagittarius"] = sagittarius()
signIcons["capricorn"] = capricorn()
signIcons["aquarius"] = aquarius()
signIcons["pisces"] = pisces()
for shape in [Shape](self.signIcons.values) {
shape.strokeEnd = 0.001 //in combination with the next two settings
shape.lineCap = .Round //strokeEnd 0.001 makes a round dot at
shape.lineJoin = .Round //the beginning of the shape's path
shape.transform = Transform.makeScale(0.64, 0.64, 1.0)
shape.lineWidth = 2
shape.strokeColor = white
shape.fillColor = clear
}
}
我们从库里拿出来的符号还是原始符号,我们需要缩小一下,如果我们不进行这行操作:hape.transform = Transform.makeScale(0.64, 0.64, 1.0)
,符号的形状就会超级大,如下图:
3. 目标位置
接下来,我们需要计算每个形状的目标位置,存储到两个数组中,在类里添加下面的代码:
var innerTargets : [Point]!
var outerTargets : [Point]!
接着添加下列方法:
func positionSignIcons() {
innerTargets = [Point]()
let provider = AstrologicalSignProvider.sharedInstance
let r = 10.5
let dx = canvas.center.x
let dy = canvas.center.y
for i in 0..<provider.order.count {
let ϴ = M_PI/6 * Double(i)
let name = provider.order[i]
if let sign = signIcons[name] {
sign.center = Point(r * cos(ϴ) + dx, r * sin(ϴ) + dy)
canvas.add(sign)
sign.anchorPoint = Point(0.5,0.5)
innerTargets.append(sign.center)
}
}
outerTargets = [Point]()
for i in 0..<provider.order.count {
let r = 129.0
let ϴ = M_PI/6 * Double(i) + M_PI/12.0
outerTargets.append(Point(r * cos(ϴ) + dx, r * sin(ϴ) + dy))
}
}
上述过程使用了以下概念:
- 创建形状,通过
anchorPoint
方法来给每个形状设置起始点 - 调用
shape.center
实际上返回的是形状的位置anchorPoint
,相对于每个形状的superview
- 调整
anchorPoint
到形状的中心(例如{0.5,0.5}
),不会改变形状的位置 - 重置
anchorPoint
后,我们能够获取形状实际的中心位置,作为目标
最后,在 createSignIcons
的最后,给所有的符号都设置了风格后,添加下列代码:
positionSignIcons()
现在,让我们看一下如何将这些排列在一起。
3.1 检查一下
将菜单的 backgroundColor
值改成 C4Purple
,创建符号图标,setup()
方法应该像下面这样:
public override func setup() {
canvas.backgroundColor = COSMOSbkgd
createSignIcons()
}
到 WorkSpace
里,更新 setup()
方法,如下:
override func setup() {
canvas.add(MenuIcons().canvas)
}
运行程序,效果如下:
4 图标的动画效果
有四个不同的动画需要创建:位置的出/入,形状的出现/隐藏。所以,创建四个动画变量:
var signIconsOut : ViewAnimation!
var signIconsIn : ViewAnimation!
var revealSignIcons : ViewAnimation!
var hideSignIcons : ViewAnimation!
增加下列方法:
func createSignIconAnimations() {
}
需要四个动画是由于设计原因,的吧是线在出入时不同的方向有不同的顺序。
出:移动,出现;入:隐藏,移动。
由于模式正好相反,当动画开始后,我们需要获取单独的动画来应对移动和隐藏,形成序列。在方法里添加如下内容:
revealSignIcons = ViewAnimation(duration: 0.5) {
for sign in [Shape](self.signIcons.values) {
sign.strokeEnd = 1.0
}
}
revealSignIcons?.curve = .EaseOut
hideSignIcons = ViewAnimation(duration: 0.5) {
for sign in [Shape](self.signIcons.values) {
sign.strokeEnd = 0.001
}
}
hideSignIcons?.curve = .EaseOut
需要标注一下:
- 将每个动画分开,更容易在每一步进行自定义
- 能够明确每个方向的持续时间
- 把 stroke 设置回
0.001
,保存点
现在,把下列代码添加到 createSignIconAnimations
:
signIconsOut = ViewAnimation(duration: 0.33) {
for i in 0..<AstrologicalSignProvider.sharedInstance.order.count {
let name = AstrologicalSignProvider.sharedInstance.order[i]
if let sign = self.signIcons[name] {
sign.center = self.outerTargets[i]
}
}
}
signIconsOut?.curve = .EaseOut
//把图标移动到结束位置
signIconsIn = ViewAnimation(duration: 0.33) {
for i in 0..<AstrologicalSignProvider.sharedInstance.order.count {
let name = AstrologicalSignProvider.sharedInstance.order[i]
if let sign = self.signIcons[name] {
sign.center = self.innerTargets[i]
}
}
}
signIconsIn?.curve = .EaseOut
我们在这里获取了图标,设置图标在内环时的中心点和在外环时的中心点。
4.1 查看一下效果
调用出现和移动动画来测试一下,如下:
func animOut() {
delay(1.0) {
self.signIconsOut?.animate()
}
delay(1.5) {
self.revealSignIcons?.animate()
}
delay(2.5) {
self.animIn()
}
}
func animIn() {
delay(0.25) {
self.hideSignIcons?.animate()
}
delay(1.0) {
self.signIconsIn?.animate()
}
delay(2.5) {
self.animOut()
}
}
在 setup()
里调用 animOut()
,如下:
public override func setup() {
canvas.backgroundColor = COSMOSbkgd
createSignIcons()
createSignIconAnimations()
animOut()
}
效果如下:
5. 打扫一下
删除 animIn
和 animOut
方法,修改 setup()
方法如下:
public override func setup() {
canvas.frame = Rect(0,0,80,80)
canvas.backgroundColor = clear
createSignIcons()
createSignIconAnimations()
}
获取 MenuIcons.swift.
干净利索!
本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权。