美文网首页
第 16 章:圆环菜单

第 16 章:圆环菜单

作者: sing_crystal | 来源:发表于2016-05-15 21:33 被阅读42次

    原文链接
    作者: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 翻译组翻译,已经获得作者翻译授权。

    相关文章

      网友评论

          本文标题:第 16 章:圆环菜单

          本文链接:https://www.haomeiwen.com/subject/thaprttx.html