第 16 章:圆环菜单

2016-05-15  本文已影响42人  sing_crystal

原文链接
作者:C4 开源项目
译者:Crystal Sun
全部章节请关注此文集C4教程翻译
校对后的内容请看这里

我们的主要目标是完成菜单的两种状态,我们会创建一些结构体,完成两种状态间的转换。例如,我们不再给一条厚线创建另外一个动画,而是创建一个数组,保存我们的目标 frame,在动画过程中来更新指环 frame 的大小。我们会在之后的演示中创建所需的结构体。

指环

这些线是菜单的骨架,我们把这个组件分解成几个部分:创建所有的指环,设置开始状态,设置结束状态,最后动画。

先创建厚线、细线、竖线和分割线。

厚指环

先布局厚指环,这里有两个:一个是小的,直径 28pt,另外一个是大的,直径 450pt。

打开 MenuRings.swift 创建一个可变数组,存储厚指环的尺寸。

var thickRingFrames : [Rect]!

接着,给指环创建一个变量:

var thickRing : Circle!

接着,写一个方法来创建厚指环,设置 targets(主体)的内外部的形状,接着把它们的 frames 添加到数组里:

func createThickRing() {
    //创建两个形状
    let inner = Circle(center: canvas.center, radius: 14)
    let outer = Circle(center: canvas.center, radius: 225)
    //存储每个位置的 frames
    thickRingFrames = [inner.frame,outer.frame]
}

之后我们会使用这个 frame 数组来实现我们的动画。现在嘛,我们先实现指环的开始状态。把下列代码添加到 createThickRing 函数里最下方:

inner.fillColor = clear
inner.lineWidth = 3
inner.strokeColor = COSMOSblue
inner.interactionEnabled = false
thickRing = inner

canvas.add(thickRing)

检查一下

为了查看一下你刚刚添加的代码的实际效果,需要在 WorkSpace 里添加 setup() 方法:

canvas.add(MenuRings().canvas)

App 现在看起来应该是下图这个样子:

要看一下指环处于最外层时的状态,在 MenuRings.swift 文件里,需要把下行代码:

thickRing = inner

换成这样:

thickRing = inner
thickRing.frame = outer.frame

就能看到下图这样的效果了:

在继续下一步之前,撤销刚刚做出的修改。

瘦指环

创建瘦指环的过程也是一样的,不同之处在于有多个瘦指环。先创建两个数组:

var thinRings : [Circle]!
var thinRingFrames : [Rect]!

我们给瘦指环创建数组,因为不同于厚指环的地方在于,我们需要记录五个瘦指环。

设计稿也详细说明了开始状态下的内层指环的直径是 8pt,外层指环的直径是 56pt、78pt、98pt 和 156pt。

创建一个方法,设置和建造全部的指环,包括内层和外层的:

func createThinRings() {
    thinRings = [Circle]()
    thinRings.append(Circle(center: canvas.center, radius: 8))
    thinRings.append(Circle(center: canvas.center, radius: 56))
    thinRings.append(Circle(center: canvas.center, radius: 78))
    thinRings.append(Circle(center: canvas.center, radius: 98))
    thinRings.append(Circle(center: canvas.center, radius: 102))
    thinRings.append(Circle(center: canvas.center, radius: 156))
}

我们创建了一堆外形,添加到数组里,在之后可以引用它们。

接下来,创建循环,遍历所以的外形,设置每个外形的风格。把下列代码添加到 createThinRings 方法里:

thinRingFrames = [Rect]()
for i in 0..<self.thinRings.count {
    let ring = self.thinRings[i]
    ring.fillColor = clear
    ring.lineWidth = 1
    ring.strokeColor = COSMOSblue
    ring.interactionEnabled = false
    if i > 0 {
        ring.opacity = 0.0
    }
    self.thinRingFrames.append(ring.frame)
}

for ring in thinRings {
    canvas.add(ring)
}

循环看起来很简单:遍历所有的指环,执行一系列有关风格设置的代码。指环按照从小到大的顺序添加到数组里,所以数组里的第一个元素成了内层第一个指环(例如当菜单处于默认状态时)。一开始我们只想看内层指环,所以 i>0 的指环设置透明度为 0。我们把每个指环的 frame 尺寸添加到 thinRingFrames 里,最后把所有的指环添加到 canvas 里。

更新 setup() 方法,如下:

public override func setup() {
    createThickRing()
    createThinRings()
}

检查一下

运行程序,应用看起来正如下图所示:

为了看一下外层指环,在 createThinRings() 方法里,把下列代码:

if i > 0 

改成这样:

if i == 0

现在,App 看起来应该是这样的:

撤销刚刚做的修改。

竖线

正如我们上面两次所做的事情,我们要创建一个数组来存储竖线。实际上竖线最终要填到指环上,所以我们不需要存储竖线的 targets(主体)。

把下列变量添加到类中:

var dashedRings : [Circle]!

创建三个方法:

func createShortDashedRing(){
}
func createLongDashedRing(){
}
func createDashedRings() {
    dashedRings = [Circle]()
    createShortDashedRing()
    createLongDashedRing()
    
    for ring in self.dashedRings {
        ring.strokeColor = COSMOSblue
        ring.fillColor = clear
        ring.interactionEnabled = false
        ring.lineCap = .Butt
        self.canvas.add(ring)
    }
}

我们需要创建长短两种竖线,胖瘦程度不同,每种竖线都有自己的模式。

短竖线指环

对短竖线的设置如下:

func createShortDashedRing() {
    let shortDashedRing = Circle(center: canvas.center, radius: 82+2)
    let pattern = [1.465,1.465,1.465,1.465,1.465,1.465,1.465,1.465*3.0] as [NSNumber]
    shortDashedRing.lineDashPattern = pattern
    shortDashedRing.strokeEnd = 0.995
    
    let angle = degToRad(-1.5)
    let rotation = Transform.makeRotation(angle)
    shortDashedRing.transform = rotation
    
    shortDashedRing.lineWidth = 0.0
    dashedRings.append(shortDashedRing)
}

这里实际上进行了很多设置,有的设置还依赖于其他的设计因素,所以我在这里将上述代码尽可能地分解:

  1. 圆圈的直径是 82+2,我本来可以写 84 的,不过 +2 实际上值得是 lineWidth 的一半。
  2. pattern1.462,....,使用这个数字实际上是有两个原因的:a)圆圈可以分成 36 个部分,每个部分 10 度,b)1.465*3 表示间隙(例如每个长竖线之间间隔两个空格,外加一个额外的空格,一个三个空格),c)as [NSNumber] 必须要有,因为底层属性需要(例如不是 [Double])。
  3. 因为模式会从第一条线开始,所以我们需要旋转一点整个图形,这样看起来好像起始位置是空格了,旋转度为 -1.5 度,转换成弧度,创建 transform,应用到形状上。
  4. 剩下的内容都简单易懂无需解释了。

长竖线指环

longDashedRing 方法如下:

func createLongDashedRing() {
    let longDashedRing = Circle(center: canvas.center, radius: 82+2)
    longDashedRing.lineWidth = 0.0

    let pattern = [1.465,1.465*9.0] as [NSNumber]
    longDashedRing.lineDashPattern = pattern
    longDashedRing.strokeEnd = 0.995

    let angle = degToRad(0.5)
    let rotation = Transform.makeRotation(angle)
    longDashedRing.transform = rotation

    let mask = Circle(center: longDashedRing.bounds.center, radius: 82+4)
    mask.fillColor = clear
    mask.lineWidth = 8
    longDashedRing.layer?.mask = mask.layer

    dashedRings.append(longDashedRing)
}

其实和之前的方法很相似,有几个调整:

  1. 模式是 [1.465,1.465*9.0],表示每个间隙之后有一个竖线,间隙的宽度比竖线宽 9x。
  2. 旋转 5 度,将长竖线居中,正好在短竖线间隙的中间。
  3. 最后一条线有微小的差异,最后一条线稍微可见,所以把 strokeEnd 的值从 1 调整成 0.995,隐藏一下。
  4. 接着,创建面具...

更新 setup() 如下:

public override func setup() {
    createThickRing()
    createThinRings()
    createDashedRings()
}

检查一下

要看指环什么样子,需要做一下操作:

把这行代码:

shortDashedRing.lineWidth = 0.0

改成:

shortDashedRing.lineWidth = 4.0

把这行代码:

longDashedRing.lineWidth = 0.0 

改成:

longDashedRing.lineWidth = 12.0

运行,效果如下图:

把上面的两个变动再改回去。

面具

面具组件算是个小把戏,在塑造竖线的外形方面,能减轻工作量。默认情况下,竖线是画在中心的外围的,如果你前面已经有了一条 12pt 水平的线,那么上面和下面的线是 6pt。

设计图显示,两个圆圈的初始状态下的直径都是一样的。我们想让竖线看起来像是从基线向外生长...所以,我们给多余的部分盖上面具,就看不到多余的部分了。

这也是属性 12pt 的原因,但是在屏幕上看起来只有 6pt...因为砍掉了 6pt

面具是这样工作的:
所以,我们创建一个 8pt 的实线,
如下:

欧耶,当你将面具应用到某个对象上时,面具对得到对象内部空间的坐标值,这与为什么我们需要 longDashedRing.bounds.center 来确定面具的中心点。

分割线

下一步创建分割线,将每个图标分隔开来。

首先,创建如下变量:

var menuDividingLines : [Line]!

接着,在类里增加如下方法:

func createMenuDividingLines() {
    menuDividingLines = [Line]()
    for i in 0...11 {
        let line = Line((Point(),Point(54,0)))
        line.anchorPoint = Point(-1.88888,0)
        line.center = canvas.center
        line.transform = Transform.makeRotation(M_PI / 6.0 * Double(i) , axis: Vector(x: 0, y: 0, z: -1))
        line.lineCap = .Butt
        line.strokeColor = COSMOSblue
        line.lineWidth = 1.0
        line.strokeEnd = 0.0
        canvas.add(line)
        menuDividingLines.append(line)
    }
}

设置直线的步骤相当简单,我们知道图标内部和外部边缘直接的间隙是 54pt,那么我们要画的这条分割线也是这么长,设置分割线的风格,改变 anchorPoint

锚点

每个可见的对象都有一个锚点,默认位置是在对象视图的居中位置。围绕这个点产生所以可见的转变。例如,如果我只是让一个对象旋转某个角度(正如我在长短竖线那里所做的),那么整个对象都会围绕自己的锚点旋转。

我想要的效果是,线的角度均匀地分布在两个圆圈之间,依赖于 anchorPoint 属性。我们可以计算每条线的旋转位置 ab,不过这样创建效果可不太优雅。

我们需要做的是把 anchorPoint 位置偏移,这样我们可以在线的外面围绕一点旋转。唉,还是看图片更容易理解,一图胜千言,效果如下图:

关于锚点的另外一件事情就是,它们的测量和对象视图的空间有关。具体说来,一个视图的中心点是 {0.5,0.5},所以,现在我们只需要找出我们需要把锚点放在哪里,这样,54pt 的线就会出现在正确的位置了。

已经知道内圆的半径是 102(例如,倒数第二个瘦指环),我们也知道线的宽度是 54,所以我们需要做的就是转换相关的坐标:

102/54 = 1.888

由于我们想让点在视图外部距左的距离为 0,我们需要让值是负数,也就是下面这行代码:

line.anchorPoint = CGPointMake(-1.88888,0)

方法中剩下的部分都很简单了,把锚点居中,位于 canvas 的中心部分,然后旋转分割线,12 条线都进行同样的操作后,把它们添加到 canvas 和数组里(之后会玩出更多花样的)。

V5。分割线已完成。

哦对了,别忘饿了,如果我们没有调整分割线的锚点,布局看起来应如下图所示:

setup() 看起来应该是这个样子的:

override func setup() {
   self.createThickRing()
   self.createThinRings()
   self.createDashedRings()
   self.createMenuDividingLines()
}

检查一下

想看到分割线,在 createMenuDividingLines 里进行修改:

把下面的代码删除:

line.strokeEnd = 0.0

改成:

line.strokeEnd = 1.0

或者注释掉也行。

效果如下图:

如果你想到原来的样子,更改所有之前的变量,看一下指环外部和直线的状态,效果如下图:

撤销刚刚做的修改,把直线设置成在里面的状态。

V5!

这些线看起来不错。

让我们继续下一章吧。

脚注

  1. 我在写的时候就在想,为什么要这样写?不过之后又看了一遍代码之后,我意识到,我有点喜欢这样了,能我记住中心点需要调整一下,虽然 Jake 设计稿里明确直径是 82pt。
  2. 注意我正在对 z 轴应用一个旋转,不过也可以应用在 x 或 y 轴上。

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权。

上一篇下一篇

猜你喜欢

热点阅读