在AsyncDisplayKit2.0
教程的第二部分中,了解在iOS應(yīng)用程序中構(gòu)建快速靈活的布局是多么容易。
AsyncDisplayKit
的布局系統(tǒng)讓你以聲明式語法編寫布局代碼曾撤,快到令人難以置信套才。你可以編寫一個能夠在view controller
或者iPad app
的popover
中使用的節(jié)點(diǎn)。如果正確編寫它的布局的話,你可以將這個節(jié)點(diǎn)遷移到新的環(huán)境中,而無需修改布局代碼!
在本教程中伍俘,你將使用第一課中用過的CardNode
類,并利用它來學(xué)習(xí)布局約束勉躺。你會看到要實(shí)現(xiàn)它的布局約束是多么的簡單癌瘾,這正是你所期望的。
一: 自動布局的問題
你也許會問“用自動布局不行嗎?”饵溅。使用自動布局妨退,每個約束都會以表達(dá)式的形式放在表達(dá)式系統(tǒng)中。也就是說蜕企,每個約束都讓約束解析器的執(zhí)行時間以指數(shù)級增長咬荷。這種計(jì)算總是在主線程中進(jìn)行的。
ASDK 的設(shè)計(jì)目標(biāo)之一轻掩,就是盡可能地接近 UIKit
的API
幸乒。不幸的是,自動布局是一個封閉的系統(tǒng)唇牧,無法告訴布局解析器在另一個線程中完成這個工作罕扎。
二: 開始
要開始,請?jiān)?a target="_blank" rel="nofollow">此處下載入門項(xiàng)目丐重。因?yàn)榻酉聛韺W(xué)習(xí)的內(nèi)容和布局規(guī)范相關(guān)腔召,我們需要將本系列第一課中的完成項(xiàng)目的版本進(jìn)行修改。
注意:在完成AsyncDisplayKit 2.0教程之前扮惦,請務(wù)必查看第1部分臀蛛,了解Async Display Kit的介紹。
三: ASLayoutSpec 介紹
首先崖蜜,科普一點(diǎn)歷史浊仆。
布局規(guī)范在構(gòu)建 Paper 大事記中提到的一個布局系統(tǒng)烙肺。目的是讓節(jié)點(diǎn)及其子節(jié)點(diǎn)的位置、大小的計(jì)算和應(yīng)用規(guī)范化和可重用氧卧。
在 ASDK 1.9.x 中,你可以創(chuàng)建異步的布局氏堤,但布局代碼和 UIKit 中預(yù)布局的方式差不多沙绝。節(jié)點(diǎn)的子節(jié)點(diǎn)的大小在一個 calculateSizeThatFits: 方法中計(jì)算。這個 size 可被緩存并在隨后的 -layout 方法中應(yīng)用鼠锈。節(jié)點(diǎn)的位置仍然需要進(jìn)行老舊的數(shù)學(xué)方法計(jì)算——沒有人喜歡和數(shù)學(xué)計(jì)算打交道闪檬。
呃,好吧购笆,大部分人都不喜歡和數(shù)學(xué)打交道粗悯!
好的,很好同欠,大多數(shù)人不喜歡搞亂數(shù)學(xué)样傍!
四: Layout Specs
使用ASDK 2.0,ASDisplayNode
子類會實(shí)現(xiàn) -layoutSpecThatFits
方法铺遂。 ASLayoutSpec對象確定所有子節(jié)點(diǎn)的大小和位置衫哥。在這樣做時,布局規(guī)范還確定所述父節(jié)點(diǎn)的大小.
一個節(jié)點(diǎn)會返回一個layout spec
對象——通過 -layoutSpecThatFits:
方法襟锐。這個對象會結(jié)算節(jié)點(diǎn)的大小撤逢,并循環(huán)計(jì)算它的所有子節(jié)點(diǎn)的大小和位置。
ThatFits
方法參數(shù)是一個ASSizeRange
粮坞。它有兩個CGSize
屬性蚊荣,一個最小size
,一個最大size
莫杈。分別定義該節(jié)點(diǎn)的最小尺寸和最大尺寸互例。
ASDK 提供了很多種layout spec
。它們是:
- 1:
ASStackLayoutSpec
: 允許你定義一個水平或垂直的子節(jié)點(diǎn)棧姓迅。它的justifyContent
屬性決定棧在相應(yīng)方向上的子節(jié)點(diǎn)之間的間距敲霍。alignItems
屬性決定了它們在另一個坐標(biāo)軸上的間距。這種layout specs
有點(diǎn)像UIKit
的UIStackView
- 2:
ASOverlayLayoutSpec
: 允許你拉伸一個元素橫跨到另一個元素丁存。被覆蓋的對象必須要有一個固定的content size
肩杈,否則無法工作 - 3:
ASRelativeLayoutSpec
: 一種相對布局,允許將一個東西以相對位置放置在它的有效空間內(nèi)解寝。參考一下“9-切片圖”的 9 個切片扩然。你可以讓一個東西放在這 9 個切片中的某個上。. - 4:
ASInsetLayoutSpec
: 一個 inset 布局聋伦,允許你在一個已有的對象的基礎(chǔ)上添加某些間距夫偶。你想在你的 cell 四周加上經(jīng)典的 iOS 16 像素的邊距嗎界睁?用這個就對了。
五: ASLayoutElement協(xié)議
Layout specs
負(fù)責(zé)管理一個或多個子節(jié)點(diǎn)兵拢。一個layout spec
的子節(jié)點(diǎn)應(yīng)該是一個節(jié)點(diǎn)比如ASTextNode
或ASImageNode
翻斟。或者除了節(jié)點(diǎn)之外说铃,layou spec
的子節(jié)點(diǎn)也可以是另一個layout spec
访惜。
噢,太不可思議了腻扇!
Layout sepc
必須實(shí)現(xiàn)ASLayoutElement
協(xié)議债热。ASLayoutSpec
和ASDisplayNode
都實(shí)現(xiàn)了ASLayoutElement
。因此這兩者及其子類都可以作為layout spec
的子節(jié)點(diǎn)幼苛。
這個簡單的概念帶來的功能無比強(qiáng)大窒篱。最重要的一種layout spec
是ASStackLayoutSpec
。它能夠?qū)⒁粡垐D片和一個文本裝在一起舶沿,也能夠?qū)⒁粡垐D片和另一個 stack 裝在一起墙杯。
你猜對了。該實(shí)戰(zhàn)檢驗(yàn)一下了暑椰!我指的是敲代碼…
六: 添加動物圖片
假設(shè)在你上班的時候霍转,設(shè)計(jì)師發(fā)來一張圖片,她希望新的動物百科 app 應(yīng)當(dāng)長成這個樣子:
為了表達(dá)整個布局一汽,首先需要將它分解成幾個對應(yīng)的layout spec
避消。有時候可能會覺得無所適從,但別忘了召夹,layout spec 的威力取決于它們能夠組合起來有多容易岩喷。開始越簡單越好。
稍稍的劇透一下监憎,你可以用一個 stack 將上半部分和下半部分組合起來就是了∩匆猓現(xiàn)在你明白了吧,你可以分別對兩個分開的部分單獨(dú)布局鲸阔,然后將它們組合在一起偷霉。
解壓縮啟動項(xiàng)目并打開RainforestStarter.xcworkspace
。找到CardNode.m
的layoutSpecThatFits
方法『稚福現(xiàn)在它只是簡單地返回一個空的ASLayoutSpec
對象类少。
如果你運(yùn)行 app,你會看到:
呃渔扎,這只是開頭硫狞。首先來一張動物圖片怎樣?
默認(rèn),一個網(wǎng)絡(luò)圖片節(jié)點(diǎn)沒有內(nèi)容残吩,當(dāng)然也沒有固定尺寸(intrinsic size)财忽。當(dāng)然,從截屏圖中你可以算出動物圖片大概占據(jù)整屏寬度和 2/3 的屏幕高度泣侮。
要實(shí)現(xiàn)這個即彪,將 return 語句替換為:
//1
CGFloat ratio = constrainedSize.min.height/constrainedSize.min.width;
//2
ASRatioLayoutSpec *imageRatioSpec = [ASRatioLayoutSpec
ratioLayoutSpecWithRatio:ratio
child:self.animalImageNode];
//3
return imageRatioSpec;
依次記錄每個編號的評論:
1: 計(jì)算比例:首先以高/寬比的形式定義圖片所用的比例。這里活尊,你指定圖片的高度為 cell 最小高度即屏幕高度的 2/3祖凫。
2: 創(chuàng)建一個
ratio layout spec
然后,創(chuàng)建一個ASRatioLayoutSpec
酬凳,使用我們算好的比例以及一個子節(jié)點(diǎn)animalImageNode
作為參數(shù)。3: 返回 spec:返回
imageRatioSpec
遭庶,用于指定 cell 的寬高宁仔。
運(yùn)行 app,看看你的 layout spec 是什么樣子:
呃峦睡,這么簡單翎苫?因?yàn)橹挥袌D片擁有 size,cell 會自動被它撐大榨了。
注意:傳遞給
cell
的constrainedSize
由一個最小值(0,0)
和一個最大值(tableNodeWidth,INF)
構(gòu)成煎谍,這是為什么需要用perfrerredFrameSize
作為圖片高度的原因。preferredFrameSize
在第一課的 AnimalPageController 中進(jìn)行賦值龙屉。
七: 添加漸變
現(xiàn)在您已擁有動物圖像呐粘,下一個邏輯步驟是在其上添加漸變節(jié)點(diǎn)。 ASOverlayLayoutSpec
只是作業(yè)的規(guī)范转捕。
首先作岖,在imageRatioSpec
初始化后添加以下行:
ASOverlayLayoutSpec *gradientOverlaySpec = [ASOverlayLayoutSpec
overlayLayoutSpecWithChild:imageRatioSpec
overlay:self.gradientNode];
在創(chuàng)建自己的layout spec
時,總是用一個spec
將其他spec
包含起來五芝。目前痘儡,這個 spec 就是gradientOverlaySpec
。
將 return 語句替換為:
return gradientOverlaySpec;
運(yùn)行 app枢步,可以看到每個 imageNode 上面都會覆蓋以一個漸變色沉删。
每只鳥都覆蓋著一層漸變色——很漂亮!
八: 加入動物名稱
上半部唯一要做的就是顯示動物的名字醉途。
要完成上半部分矾瑰,還剩下一件事情就是:顯示動物名稱。
這好像不是太難结蟋,只需要注意幾個地方:
- 1: 名字應(yīng)當(dāng)放在漸變層之上脯倚。
- 2:名字應(yīng)當(dāng)位于動物圖片的左下角
- 3: 左邊留白 16 個像素,底部留白 8 個像素
你已經(jīng)知道如何將文字放在哪個布局上面了。現(xiàn)在讓我們付諸實(shí)踐推正。
在gradientOverlaySpec
之后添加以下行恍涂。
ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec
overlayLayoutSpecWithChild:gradientOverlaySpec
overlay:self.animalNameTextNode];
然后,將 return 語句改成:
return nameOverlaySpec;
運(yùn)行程序植榕,你能看到文字了:
不錯再沧,現(xiàn)在需要把它挪到左下角。
這時需要解釋一下了尊残。在鳥的圖片上有一些文字炒瘸,因此你很自然地會將 nameOverlaySpec 添加到另一個 spec 中并放到指定的位置。這時你需要后頭想想寝衫,你到底想要什么顷扩。
這里,你用 nameOverlaySpec 去包含某個對象慰毅,它是獨(dú)立于已有內(nèi)容之外的隘截。
但你并不真的想把名字包含在內(nèi)容中。你想要的是汹胃,告訴這個名字婶芭,它應(yīng)當(dāng)位于它的有效空間的左下角,然后將 spec 拉伸到占滿整個有效空間着饥。
九: ASRelativeLayoutSpec介紹
你真正需要的是ASRelativeLayoutSpec
犀农。
ASRelativeLayoutSpec
用一個ASLayoutElement
作為子對象,并把這個空間作為它的有效空間宰掉,然后根據(jù)你的需要放置子對象呵哨。
定義相對規(guī)范時,可以設(shè)置其verticalPosition
和horizo??ntalPosition
屬性轨奄。
這兩個屬性可以是以下之一:
- ASRelativeLayoutSpecPositionStart
- ASRelativeLayoutSpecPositionCenter
- ASRelativeLayoutSpecPositionEnd
這些值可以組合仇穗,允許你把對象放在某個角、某條變或者它的有效空間的中央戚绕。
給你布置個作業(yè)纹坐,你能將這只青蛙放在它的有效空間的右邊嗎?
如果你回答“將verticalPostion
設(shè)為ASRelativeLayoutSpecPositionCenter
舞丛,將horizontalPosition
設(shè)為ASRelativeLayoutSpecPositionEnd
就對了!
現(xiàn)在再來點(diǎn)難一點(diǎn)的耘子,下面這句將更有感覺。在nameOverlaySpec
一句前加入這行:
ASRelativeLayoutSpec *relativeSpec = [ASRelativeLayoutSpec
relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
verticalPosition:ASRelativeLayoutSpecPositionEnd
sizingOption:ASRelativeLayoutSpecSizingOptionDefault
child:self.animalNameTextNode];
如你所見球切,我們將子對象的horiazontalPosition
設(shè)為start
谷誓,verticalPosition
設(shè)為end
。用青蛙的話來說吨凑,它應(yīng)該是這樣:
現(xiàn)在你已經(jīng)創(chuàng)建了一個relative spec
捍歪,將nameOverlaySpec
定義修改為:
ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec
overlayLayoutSpecWithChild:gradientOverlaySpec
overlay:relativeSpec];
運(yùn)行 app户辱,你會看見:
好了!cell 的上半部分只剩下一件事情要干了糙臼。
十: ASInsetLayoutSpec 介紹
最后一件事情是讓動物名稱左邊留白 16 像素庐镐,下邊留白 8 個像素。這需要用到ASInsetLayoutSpec
变逃。
要在對象的四邊添加留白必逆,只需將對象放到 inset spec 中,并提供 UIEdgeInsets 來指定需要留白多少像素揽乱。
在nameOverlaySpec
之后添加一句:
ASInsetLayoutSpec *nameInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 16.0, 8.0, 0.0) child:nameOverlaySpec];
然后名眉,將return
語句修改成返回最外層的spec
:
return nameInsetSpec;
運(yùn)行程序,你會看到:
你不需要將 inset 應(yīng)用到整個 overlay 所包含的空間凰棉,因?yàn)樗€包含了動物圖片损拢。
你真正的目的是將 inset 應(yīng)用到relativeSpec
的有效空間。要解決這個問題撒犀,首先刪除當(dāng)前的nameInsetSpec
定義探橱。
然后,在 nameOverlaySpec 定義之前加入這個修改過的版本:
ASInsetLayoutSpec *nameInsetSpec = [ASInsetLayoutSpec
insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 16.0, 8.0, 0.0) child:relativeSpec];
現(xiàn)在绘证,你需要讓nameOverlaySpec
去包含inset spec
,而不是relativeSpec
哗讥。將原來的nameOverlaySpec
聲明修改為:
ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:gradientOverlaySpec overlay:nameInsetSpec];
然后修改 return 語句:
return nameOverlaySpec;
運(yùn)行 app嚷那,正是你希望看到的:
十一: 下半部分
下半部分非常簡單,將動物的介紹用一個inset
包圍住… 你懂的杆煞。
在return
語句之前用動物介紹創(chuàng)建一個inset spec
魏宽。
ASInsetLayoutSpec *descriptionTextInsetSpec = [ASInsetLayoutSpec
insetLayoutSpecWithInsets:UIEdgeInsetsMake(16.0, 28.0, 12.0, 28.0) child:self.animalDescriptionTextNode];
當(dāng)你返回這個 spec 并運(yùn)行 app,你會看到:
這正是你想要的【龊酰現(xiàn)在你已經(jīng)弄好上下兩部分了队询,將它們合在一起是小菜一碟。
十二: 固有內(nèi)容尺寸
你也許注意到了构诚,你不需要擔(dān)心文字的內(nèi)容的尺寸如何填充這個空間蚌斩。因?yàn)?code>ASTextNode 有一個固有內(nèi)容尺寸 (intrinsic content size
),它會根據(jù) text 屬性和 attributes 屬性自動計(jì)算范嘱。
下列節(jié)點(diǎn)沒有默認(rèn)尺寸:
- ASDisplayNode 子類
- ASNetworkImageNode 和 ASMultiplexImageNode
- ASVideoNode 和 ASVideoPlayerNode
通常這些節(jié)點(diǎn)一開始都沒有內(nèi)容送膳,因此無法定義它們自己的大小。這些節(jié)點(diǎn)要么需要設(shè)置preferredFrameSize
丑蛤,要么在它們擁有具體 size 之前放到一個layout spec
中叠聋。
十三: ASStackLayoutSpec介紹
該說說stack layout spec
了。你可以把它看成是一種和UIStackView
類似的layout spec
受裹,但它能夠自動向后兼容碌补,這確實(shí)太爽了。 Stacks 可以定義要么橫向要么縱向,和其它layout spec
一樣厦章,它可以把其它節(jié)點(diǎn)或layout spec
作為子對象镇匀。
為了使用stack spec
,在description inset
一句后加入:
ASStackLayoutSpec *verticalStackSpec = [[ASStackLayoutSpec alloc] init];
verticalStackSpec.direction = ASStackLayoutDirectionVertical;
verticalStackSpec.children = @[nameOverlaySpec, descriptionTextInsetSpec];
這里創(chuàng)建了一個 stack闷袒,方向設(shè)置為 vertical坑律,然后將上下兩部分添加進(jìn)去。
同樣囊骤,返回新的layou spec
:
return verticalStackSpec;
運(yùn)行程序晃择,馬上就大功告成了
注意:前面說過,stack 是核心 layout spec 中比較簡單的一個也物。大部分布局都能用一種 stack 或一系列嵌套的 stack 來進(jìn)行表達(dá)宫屠。
將 stack 進(jìn)行嵌套,可以讓 stack 變得變化無窮滑蚯、極其麻煩浪蹂。要深入了解,請參考flex box froggy game 以及 Async Display Kit 文檔告材。
十四: ASBackgroundLayoutSpec 介紹
還記得你的老朋友 overlay spec
嗎坤次?它有一個原則是:在一個overlay spec
中,被覆蓋的對象必須有一個 size斥赋。
在后面的對象定義一個 size缰猴,而前面的對象將被拉伸并占據(jù)它的大小。
background spec
則截然相反疤剑。如果你有一個對象能夠確定自己的大小滑绒,你想在它下面拉伸出另一個對象,你可以使用background spec
隘膘。
例如疑故,你可以用background layout spec
將模糊的動物圖片拉伸后放到整個 stack 的后面。
要這樣做弯菊,需要增加這行:
ASBackgroundLayoutSpec *backgroundLayoutSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:verticalStackSpec background:self.backgroundImageNode];
然后將 return 語句修改為:
return backgroundLayoutSpec;
運(yùn)行 app纵势,查看最后的效果:
十五: 結(jié)束
在這里下載完成后的項(xiàng)目。另外管钳,這里可以下載 Swift 項(xiàng)目吨悍。
當(dāng)你熟悉了這些概念之后,有一個學(xué)習(xí)更多內(nèi)容的好地方就是這里蹋嵌。這真的是一道關(guān)于布局系統(tǒng)能做什么的美味大餐育瓜。
希望你喜歡本教程,有任何關(guān)于布局的問題栽烂,請?jiān)谙旅媪粞浴?/p>