原文链接
作者:C4 开源项目
译者:Crystal Sun
全部章节请关注此文集C4教程翻译
校对后的内容请看这里
3月11日,我得了一个机会,可以写一篇教程,发表在知名网站上。所以,我开始和 Jake 讨论一些能够设计、开发、发布的概念,能够抓住 C4 Swift 版本的要点,学习如何使用这个新系统来创建动画...想到:很多基础的动画出现,然后组合成一个优雅的界面,我们看了很多 UI 视频,然后头脑风暴。
Jake 想到了一个点子:
...中间出现了一个实体圆圈,点击后,稍微缩小一下(原来的90%),然后从中心接着放射出八个圆圈,每个圆圈都比之前的圆圈大一点,最外层的圆圈里有不同的图标,点击叉号关闭所有的圆圈,回到初始状态
于是我们看了很多的视频。
讨论概念。
好像行得通。
运行程序。
这就是我们的工作方式。
1. 模拟和测试
实际的应用比较简单,尽管有很多组件,设计交互界面、背景,可能花费一些时间调整,同样的,正是应用还是简单,尽管这些调整比较复杂,不过从创建到完成,这个过程能给我们提供最好的教程素材。
Jake 展示的设计稿只有一个页面,里面有一个炫酷的动画菜单,多层视差背景。我看了一下,思考如何才能让两个组件合并起来。
2. 背景
背景部分的工作比较容易分解,主要就是很多不同内容的图层在按照不同的速度移动。
里面有:
- 大星星
- 小星星
- 连接星星的线
- 三个背景星星层
- 两个星云层
这完全可以做到,在和 Jake 沟通之后,我写了一个清单列出我需要他定义的一些东西:
- 角度/指示器动画
- 单个的星座
- 三层前景风格 + 运动效果
- 三层 星星 背景风格 + 运动效果(incl. # of stars 是什么意思?)
- 两层幸运背景风格 + 运动效果
第一步,得到 layer 的数量,同时获取视差角度...需要八个,所以我先用是个来测试一下实际的效果。
本章的代码只是我在真正开发之前的一些测试展示效果,所以当你看完这章后,记得删掉在本章添加的所有代码。
class WorkSpace: CanvasController {
//创建一个空的数组变量,用来添加 layers
var layers = [UIScrollView]()
override func setup() {
//当 layer 数量小于 10时,执行循环体里的代码
repeat {
//创建一个 layer,它的 frame 值和 canvas 的 frame 值一样
let layer = UIScrollView(frame: view.frame)
//设置每层 layer 内容的大小,高度为 0 ,防止屏幕垂直滚动
layer.contentSize = CGSizeMake(layer.frame.size.width * 10, 0)
//把 layer 添加到 canvas 上以及数组里
canvas.add(layer)
layers.append(layer)
} while layers.count < 10
}
}
挺简单的吧,使用的工程的文件正是前一章中的,我在 WorkSpace 文件中添加一个 repeat 循环体,来创建新的 layer,添加到 canvas 上,直到创建完 10 layer 为止。每创建一个 layer,我都会把 layer 的 contentSize 设置的超级大(文本中,是 canvas 的二十倍宽)。设置 contentSize 的高度为 0,这样就不会垂直滚动了。
在这时,如果我运行应用,我会什么都看不到,所以我修改一下循环体里代码,给每个 layer 增加一个 label 控件。
class WorkSpace: CanvasController {
//创建空的数组变量,用来存储 layer
var layers = [InfiniteScrollView]()
override func setup() {
//当 layer 数量小于 10时,执行循环体里的代码
repeat {
//创建一个 layer,它的 frame 值和 canvas 的 frame 值一样
let layer = InfiniteScrollView(frame: view.frame)
//设置每层 layer 内容的大小,高度为 0 ,防止屏幕垂直滚动
layer.contentSize = CGSizeMake(layer.frame.size.width * 10, 0)
//把 layer 添加到 canvas 上以及数组里
canvas.add(layer)
layers.append(layer)
//创建一个中心点变量,用来定位这些 label
var center = Point(24,canvas.height/2.0)
//计算 layer 的数量(因为我们要加最后一个 layer,从 10 开始倒序添加)
let layerNumber = 10 - layers.count
//创建字体,字号是当前 layer 的数量
let font = Font(name: "AvenirNext-DemiBold", size:Double(layers.count+1) * 8.0)!
//创建运行循环体知道每个 layer 都有一个 label
repeat {
//创建一个 label
let label = TextShape(text: "\(layerNumber)", font: font)!
//居中
label.center = center
//更新中心点的位置
center.x += 130.0
//把 layer 添加到数组里
layer.add(label)
} while center.x < Double(layer.contentSize.width)
} while layers.count < 10
}
}
修改原来的设置,加入一个内嵌的 repeat 循环体,直到全部 layer 的包含一个 label —— 每个 label 基于所在的 layer 编号。
现在运行程序,应用里会出现 label 控件,不过!如果我滚动界面,只有一个 layer 在滚动...
下一步就是创建一个观察器,查看一下最上层的 layer,在滚动时将剩下的 layer 移走。在 setup 的最下方,添加下列代码:
if let top = layers.last {
//创建一个上下文变量
var c = 0
//添加 WorkSpace 作为最上层 layer 的 contentOffset 的观察者
top.addObserver(self, forKeyPath: "contentOffset", options: NSKeyValueObservingOptions.New, context: &c)
}
这一步把 WorkSpace 作为最上层 layer 的 contentOffset 的观察者。现在,让代码更漂亮一些,我创建一个函数,关联 layer 的运动轨迹,改变其他 layer 的轨迹,如下:
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer) {
//遍历所有的 layer,停在在从上数第二层的 layer 那里
for i in 0..
漂亮。现在我们知道这是个 layer 绝对会出现了...不过,这里怎么会有一堆的媒体?...测试一下,Jake 看了一下,每层星星的数量大约在 15 个,还给我一个小的白星星。
我接着把内部 repeat 循环里的 label 换成图片,如下:
//实例化中心位置,每个 layer 有 10 * 15 个星星
let starCount = layers.count * 15
canvas.backgroundColor = black
//循环,直到 starCount
for _ in 0..
运行程序,模拟器中应用效果如下:
应用在 iPhone 5 上运行良好,这个是个层次运行料号,那么剩下的问题就是审美的问题,还需要让界面更好看一些。到这时,Jake 基本上制定了背景部分的全部细节。
2.1 单个星座
12个星座的符合由三部分构成:大星星、小星星和线,Jake 用下图记录每种星座中星星的位置:
2.2 三层近景风格 + 运动效果
接下来定义三层近景 layer 里的星星怎么样运动。Jake 的想法是有一个星星移动的地方,所以我们决定使用三层 layer:大星星、小星星、和线。当应用中出现某个特定的符号时,当前的星星需要出现在特定符号的右边,接着所有的东西都在快速移动,从一个星座到另外一个星座的时候,出现非常短的线状动画。
2.3 三层 星星 背景风格 + 运动效果
接下来需要定义背景里有多少星星在动,大约是最上面 layer 的 5%、15%、20%。对每层有多少星星也有一个大概的猜测。
2.4 两层星云层背景风格 + 运动效果
继续,Jake 定义了星云和光晕的外表以及如何移动。这一步甚至比上一步还要简单,因为光晕几乎不懂,星云层大约是 10% 的速度。
2.5 角度/指示器动画
最后一个界面会在屏幕顶部出现一条竖线,with a longer dash every 20 dashes。接着,每个星座到达屏幕的中心位置时,都会出现一个更长的白线,在星座符号的下方:
2.6 最后
最后一件事,写一个清单列出即将要开发的不同的 layer,在他无限的好意下,Jake 发给我下图:
3. 菜单
菜单看起挺简单,实际上不然。唯一需要我搞懂的就是我们给这些星座符合设计什么样的动画效果。
实际上,给它们添加动画效果这事简单,难的地方在于创造它们,因为我们希望它们有自己的贝塞尔路径,创建的过程确实痛苦的,因为我们不知道他们的路径点,像是 IllUstrator 这样的软件也不给我们权限获取数据,还有,我不想写一个 SVG 导出器,那也太多余了。
那么,我们该怎么办呢?
使用 PaintCode 画出外形,接着添加曲线轨迹,保存到 Core Graphics 代码里,如下:
UIBezierPath* bezier2Path = UIBezierPath.bezierPath;
[bezier2Path moveToPoint: CGPointMake(250, 200)];
[bezier2Path addLineToPoint: CGPointMake(150, 200)];
[bezier2Path addCurveToPoint: CGPointMake(100, 150) controlPoint1: CGPointMake(122.4, 200) controlPoint2: CGPointMake(100, 177.6)];
...
[bezier2Path closePath];
当我把代码换成下面这样后:
let bezier = Path()
bezier.moveToPoint(Point(250,200))
bezier.addLineToPoint(Point(150,200))
bezier.addCurveToPoint(Point(100,150), control1:Point(122.4,200), control2:Point(100,177.6))
...
事情开始变得更清晰,更容易处理了。现在我还需要得到星座符号的外形添加到 C4 代码里,无需费太多力就能实现我们想要实现的效果。
比如,让 shape 的外形完全和要求的一样:
shape.strokeEnd = 1.0
3.1 红线
走到这一步,我准备创建菜单了,因此需要下面的红线,标注菜单上所有元素的具体的位置、尺寸等等。
Jake 的工作做的真棒,给我准备了这张图:
4. 该进行下一章了
基本的视觉概念都解释了,
现在该做一些实际的开发工作了。不过,在职之前,我总结了一些必须要表明的问题:
- 定义外形 - 我会复用很多外形,也会给它们添加动画效果,我会用自定义的贝塞尔曲线路径,而不是单单导入图片资源。
- 复杂的动画序列 - 会有非常复杂的动画序列和调速,直到得到正确的菜单外展内收的效果。
- 定义手势交互 - 我想让手势交互越简单约好,当然了还要独一无二。
- 视差 + 无限滚动视图 - 必须给应用增加视差,我需要非常小心处理,开发完成后,应用的性能表现要非常高才行。
记得删除掉 WorkSpace.swift 文件里的测试代码...只有一个空的 setup() 方法。
继续下一章!
本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权。