原文鏈接
作者:C4 開(kāi)源項(xiàng)目
譯者:Crystal Sun
全部章節(jié)請(qǐng)關(guān)注此文集C4教程翻譯
校對(duì)后的內(nèi)容請(qǐng)看這里
3月11日呈驶,我得了一個(gè)機(jī)會(huì),可以寫(xiě)一篇教程竖共,發(fā)表在知名網(wǎng)站上苗胀。所以,我開(kāi)始和 Jake 討論一些能夠設(shè)計(jì)享潜、開(kāi)發(fā)困鸥、發(fā)布的概念,能夠抓住 C4 Swift 版本的要點(diǎn)剑按,學(xué)習(xí)如何使用這個(gè)新系統(tǒng)來(lái)創(chuàng)建動(dòng)畫(huà)...想到:很多基礎(chǔ)的動(dòng)畫(huà)出現(xiàn)疾就,然后組合成一個(gè)優(yōu)雅的界面,我們看了很多 UI 視頻艺蝴,然后頭腦風(fēng)暴猬腰。
Jake 想到了一個(gè)點(diǎn)子:
...中間出現(xiàn)了一個(gè)實(shí)體圓圈,點(diǎn)擊后猜敢,稍微縮小一下(原來(lái)的90%)姑荷,然后從中心接著放射出八個(gè)圓圈盒延,每個(gè)圓圈都比之前的圓圈大一點(diǎn),最外層的圓圈里有不同的圖標(biāo)鼠冕,點(diǎn)擊叉號(hào)關(guān)閉所有的圓圈添寺,回到初始狀態(tài)
于是我們看了很多的視頻。
討論概念懈费。
好像行得通计露。
運(yùn)行程序。
這就是我們的工作方式憎乙。
1. 模擬和測(cè)試
實(shí)際的應(yīng)用比較簡(jiǎn)單票罐,盡管有很多組件,設(shè)計(jì)交互界面寨闹、背景胶坠,可能花費(fèi)一些時(shí)間調(diào)整,同樣的繁堡,正是應(yīng)用還是簡(jiǎn)單沈善,盡管這些調(diào)整比較復(fù)雜,不過(guò)從創(chuàng)建到完成椭蹄,這個(gè)過(guò)程能給我們提供最好的教程素材闻牡。
Jake 展示的設(shè)計(jì)稿只有一個(gè)頁(yè)面,里面有一個(gè)炫酷的動(dòng)畫(huà)菜單绳矩,多層視差背景罩润。我看了一下,思考如何才能讓兩個(gè)組件合并起來(lái)翼馆。
2. 背景
背景部分的工作比較容易分解割以,主要就是很多不同內(nèi)容的圖層在按照不同的速度移動(dòng)。
里面有:
- 大星星
- 小星星
- 連接星星的線
- 三個(gè)背景星星層
- 兩個(gè)星云層
這完全可以做到应媚,在和 Jake 溝通之后严沥,我寫(xiě)了一個(gè)清單列出我需要他定義的一些東西:
- 角度/指示器動(dòng)畫(huà)
- 單個(gè)的星座
- 三層前景風(fēng)格 + 運(yùn)動(dòng)效果
- 三層 星星 背景風(fēng)格 + 運(yùn)動(dòng)效果(incl. # of stars 是什么意思?)
- 兩層幸運(yùn)背景風(fēng)格 + 運(yùn)動(dòng)效果
第一步中姜,得到 layer 的數(shù)量消玄,同時(shí)獲取視差角度...需要八個(gè),所以我先用是個(gè)來(lái)測(cè)試一下實(shí)際的效果丢胚。
本章的代碼只是我在真正開(kāi)發(fā)之前的一些測(cè)試展示效果翩瓜,所以當(dāng)你看完這章后,記得刪掉在本章添加的所有代碼携龟。
class WorkSpace: CanvasController {
//創(chuàng)建一個(gè)空的數(shù)組變量兔跌,用來(lái)添加 layers
var layers = [UIScrollView]()
override func setup() {
//當(dāng) layer 數(shù)量小于 10時(shí),執(zhí)行循環(huán)體里的代碼
repeat {
//創(chuàng)建一個(gè) layer峡蟋,它的 frame 值和 canvas 的 frame 值一樣
let layer = UIScrollView(frame: view.frame)
//設(shè)置每層 layer 內(nèi)容的大小浮定,高度為 0 相满,防止屏幕垂直滾動(dòng)
layer.contentSize = CGSizeMake(layer.frame.size.width * 10, 0)
//把 layer 添加到 canvas 上以及數(shù)組里
canvas.add(layer)
layers.append(layer)
} while layers.count < 10
}
}
挺簡(jiǎn)單的吧,使用的工程的文件正是前一章中的桦卒,我在 WorkSpace 文件中添加一個(gè) repeat 循環(huán)體,來(lái)創(chuàng)建新的 layer匿又,添加到 canvas 上方灾,直到創(chuàng)建完 10 layer 為止。每創(chuàng)建一個(gè) layer碌更,我都會(huì)把 layer 的 contentSize 設(shè)置的超級(jí)大(文本中裕偿,是 canvas 的二十倍寬)。設(shè)置 contentSize 的高度為 0痛单,這樣就不會(huì)垂直滾動(dòng)了嘿棘。
在這時(shí),如果我運(yùn)行應(yīng)用旭绒,我會(huì)什么都看不到鸟妙,所以我修改一下循環(huán)體里代碼,給每個(gè) layer 增加一個(gè) label 控件挥吵。
class WorkSpace: CanvasController {
//創(chuàng)建空的數(shù)組變量重父,用來(lái)存儲(chǔ) layer
var layers = [InfiniteScrollView]()
override func setup() {
//當(dāng) layer 數(shù)量小于 10時(shí),執(zhí)行循環(huán)體里的代碼
repeat {
//創(chuàng)建一個(gè) layer忽匈,它的 frame 值和 canvas 的 frame 值一樣
let layer = InfiniteScrollView(frame: view.frame)
//設(shè)置每層 layer 內(nèi)容的大小房午,高度為 0 ,防止屏幕垂直滾動(dòng)
layer.contentSize = CGSizeMake(layer.frame.size.width * 10, 0)
//把 layer 添加到 canvas 上以及數(shù)組里
canvas.add(layer)
layers.append(layer)
//創(chuàng)建一個(gè)中心點(diǎn)變量丹允,用來(lái)定位這些 label
var center = Point(24,canvas.height/2.0)
//計(jì)算 layer 的數(shù)量(因?yàn)槲覀円幼詈笠粋€(gè) layer郭厌,從 10 開(kāi)始倒序添加)
let layerNumber = 10 - layers.count
//創(chuàng)建字體,字號(hào)是當(dāng)前 layer 的數(shù)量
let font = Font(name: "AvenirNext-DemiBold", size:Double(layers.count+1) * 8.0)!
//創(chuàng)建運(yùn)行循環(huán)體知道每個(gè) layer 都有一個(gè) label
repeat {
//創(chuàng)建一個(gè) label
let label = TextShape(text: "\(layerNumber)", font: font)!
//居中
label.center = center
//更新中心點(diǎn)的位置
center.x += 130.0
//把 layer 添加到數(shù)組里
layer.add(label)
} while center.x < Double(layer.contentSize.width)
} while layers.count < 10
}
}
修改原來(lái)的設(shè)置雕蔽,加入一個(gè)內(nèi)嵌的 repeat 循環(huán)體折柠,直到全部 layer 的包含一個(gè) label —— 每個(gè) label 基于所在的 layer 編號(hào)。
現(xiàn)在運(yùn)行程序萎羔,應(yīng)用里會(huì)出現(xiàn) label 控件液走,不過(guò)!如果我滾動(dòng)界面贾陷,只有一個(gè) layer 在滾動(dòng)...
下一步就是創(chuàng)建一個(gè)觀察器缘眶,查看一下最上層的 layer,在滾動(dòng)時(shí)將剩下的 layer 移走髓废。在 setup 的最下方巷懈,添加下列代碼:
if let top = layers.last {
//創(chuàng)建一個(gè)上下文變量
var c = 0
//添加 WorkSpace 作為最上層 layer 的 contentOffset 的觀察者
top.addObserver(self, forKeyPath: "contentOffset", options: NSKeyValueObservingOptions.New, context: &c)
}
這一步把 WorkSpace 作為最上層 layer 的 contentOffset 的觀察者。現(xiàn)在慌洪,讓代碼更漂亮一些顶燕,我創(chuàng)建一個(gè)函數(shù)凑保,關(guān)聯(lián) layer 的運(yùn)動(dòng)軌跡,改變其他 layer 的軌跡涌攻,如下:
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
//遍歷所有的 layer欧引,停在在從上數(shù)第二層的 layer 那里
for i in 0..<layers.count-1 {
//獲取當(dāng)前的 layer
let layer = self.layers[i]
//基于 layer 的位置創(chuàng)建一個(gè) mod 值(layer 0 = 0.1, layer 1 = 0.2, ...)
let mod = 0.1 * CGFloat(i+1)
//獲取最頂層 layer 偏移量的 x 值
if let x = layers.last?.contentOffset.x {
//設(shè)置內(nèi)容的偏移量是當(dāng)前 layer * mod
layer.contentOffset = CGPointMake(x*mod,0)
}
}
}
漂亮。現(xiàn)在我們知道這是個(gè) layer 絕對(duì)會(huì)出現(xiàn)了...不過(guò)恳谎,這里怎么會(huì)有一堆的媒體芝此?...測(cè)試一下,Jake 看了一下因痛,每層星星的數(shù)量大約在 15 個(gè)婚苹,還給我一個(gè)小的白星星。
我接著把內(nèi)部 repeat 循環(huán)里的 label 換成圖片鸵膏,如下:
//實(shí)例化中心位置膊升,每個(gè) layer 有 10 * 15 個(gè)星星
let starCount = layers.count * 15
canvas.backgroundColor = black
//循環(huán),直到 starCount
for _ in 0..<starCount {
//給每個(gè)星星創(chuàng)建一張圖片
let img = Image("6smallStar")!
//允許圖片可以適當(dāng)按比例縮放
img.constrainsProportions = true
//縮放圖片的寬度
img.width *= 0.1 * Double(layers.count+1)
//將中心點(diǎn)設(shè)置為 layer 隨便某個(gè)位置上
img.center = Point(Double(layer.contentSize.width)*random01(),canvas.height*random01())
//添加到數(shù)組里
layer.add(img)
}
運(yùn)行程序谭企,模擬器中應(yīng)用效果如下:
應(yīng)用在 iPhone 5 上運(yùn)行良好廓译,這個(gè)是個(gè)層次運(yùn)行料號(hào),那么剩下的問(wèn)題就是審美的問(wèn)題赞咙,還需要讓界面更好看一些责循。到這時(shí),Jake 基本上制定了背景部分的全部細(xì)節(jié)攀操。
2.1 單個(gè)星座
12個(gè)星座的符合由三部分構(gòu)成:大星星院仿、小星星和線,Jake 用下圖記錄每種星座中星星的位置:
2.2 三層近景風(fēng)格 + 運(yùn)動(dòng)效果
接下來(lái)定義三層近景 layer 里的星星怎么樣運(yùn)動(dòng)速和。Jake 的想法是有一個(gè)星星移動(dòng)的地方歹垫,所以我們決定使用三層 layer:大星星、小星星颠放、和線排惨。當(dāng)應(yīng)用中出現(xiàn)某個(gè)特定的符號(hào)時(shí),當(dāng)前的星星需要出現(xiàn)在特定符號(hào)的右邊碰凶,接著所有的東西都在快速移動(dòng)暮芭,從一個(gè)星座到另外一個(gè)星座的時(shí)候,出現(xiàn)非常短的線狀動(dòng)畫(huà)欲低。
2.3 三層 星星 背景風(fēng)格 + 運(yùn)動(dòng)效果
接下來(lái)需要定義背景里有多少星星在動(dòng)辕宏,大約是最上面 layer 的 5%、15%砾莱、20%瑞筐。對(duì)每層有多少星星也有一個(gè)大概的猜測(cè)。
2.4 兩層星云層背景風(fēng)格 + 運(yùn)動(dòng)效果
繼續(xù)腊瑟,Jake 定義了星云和光暈的外表以及如何移動(dòng)聚假。這一步甚至比上一步還要簡(jiǎn)單块蚌,因?yàn)楣鈺瀻缀醪欢窃茖哟蠹s是 10% 的速度膘格。
2.5 角度/指示器動(dòng)畫(huà)
最后一個(gè)界面會(huì)在屏幕頂部出現(xiàn)一條豎線峭范,with a longer dash every 20 dashes。接著瘪贱,每個(gè)星座到達(dá)屏幕的中心位置時(shí)虎敦,都會(huì)出現(xiàn)一個(gè)更長(zhǎng)的白線,在星座符號(hào)的下方:
2.6 最后
最后一件事政敢,寫(xiě)一個(gè)清單列出即將要開(kāi)發(fā)的不同的 layer,在他無(wú)限的好意下胚迫,Jake 發(fā)給我下圖:
3. 菜單
菜單看起挺簡(jiǎn)單喷户,實(shí)際上不然。唯一需要我搞懂的就是我們給這些星座符合設(shè)計(jì)什么樣的動(dòng)畫(huà)效果访锻。
實(shí)際上褪尝,給它們添加動(dòng)畫(huà)效果這事簡(jiǎn)單,難的地方在于創(chuàng)造它們期犬,因?yàn)槲覀兿M鼈冇凶约旱呢惾麪柭窂胶友疲瑒?chuàng)建的過(guò)程確實(shí)痛苦的,因?yàn)槲覀儾恢浪麄兊穆窂近c(diǎn)龟虎,像是 IllUstrator 這樣的軟件也不給我們權(quán)限獲取數(shù)據(jù)璃谨,還有,我不想寫(xiě)一個(gè) SVG 導(dǎo)出器鲤妥,那也太多余了佳吞。
那么,我們?cè)撛趺崔k呢棉安?
使用 PaintCode 畫(huà)出外形底扳,接著添加曲線軌跡,保存到 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];
當(dāng)我把代碼換成下面這樣后:
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))
...
事情開(kāi)始變得更清晰衷模,更容易處理了。現(xiàn)在我還需要得到星座符號(hào)的外形添加到 C4 代碼里蒲赂,無(wú)需費(fèi)太多力就能實(shí)現(xiàn)我們想要實(shí)現(xiàn)的效果阱冶。
比如,讓 shape 的外形完全和要求的一樣:
shape.strokeEnd = 1.0
3.1 紅線
走到這一步凳宙,我準(zhǔn)備創(chuàng)建菜單了熙揍,因此需要下面的紅線,標(biāo)注菜單上所有元素的具體的位置氏涩、尺寸等等届囚。
Jake 的工作做的真棒有梆,給我準(zhǔn)備了這張圖:
4. 該進(jìn)行下一章了
基本的視覺(jué)概念都解釋了,
現(xiàn)在該做一些實(shí)際的開(kāi)發(fā)工作了意系。不過(guò)泥耀,在職之前,我總結(jié)了一些必須要表明的問(wèn)題:
- 定義外形 - 我會(huì)復(fù)用很多外形蛔添,也會(huì)給它們添加動(dòng)畫(huà)效果痰催,我會(huì)用自定義的貝塞爾曲線路徑,而不是單單導(dǎo)入圖片資源迎瞧。
- 復(fù)雜的動(dòng)畫(huà)序列 - 會(huì)有非常復(fù)雜的動(dòng)畫(huà)序列和調(diào)速夸溶,直到得到正確的菜單外展內(nèi)收的效果。
- 定義手勢(shì)交互 - 我想讓手勢(shì)交互越簡(jiǎn)單約好凶硅,當(dāng)然了還要獨(dú)一無(wú)二缝裁。
- 視差 + 無(wú)限滾動(dòng)視圖 - 必須給應(yīng)用增加視差,我需要非常小心處理足绅,開(kāi)發(fā)完成后捷绑,應(yīng)用的性能表現(xiàn)要非常高才行。
記得刪除掉 WorkSpace.swift 文件里的測(cè)試代碼...只有一個(gè)空的 setup() 方法氢妈。
繼續(xù)下一章粹污!
本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán)首量。