官方文檔連接:texturegroup.org/docs/gettin…html
[譯] AsyncDisplayKit/Texture 官方文檔(1) node
Layout API 的出現是爲了提供一種能夠替代 UIKit Auto Layout 的高性能方案,UIKit Auto Layout 在複雜的視圖結構中,計算量會呈指數級增加,Texture 的佈局方案相對 Auto Layout 有如下優勢:git
熟悉 Flexbox 的人會注意到這兩個系統有許多的類似之處, 但 Layout API 並無從新實現全部的 CSS。github
Texture 的佈局主要圍繞兩個概念展開:web
佈局規則沒有物理存在,它經過充當 LayoutElements
的容器,理解多個 LayoutElements
之間的關聯,完成 LayoutElements
的位置排列。Texture 提供了 ASLayoutSpec
的幾個子類,涵蓋了從插入單個佈局元素的簡單規則,到能夠變化堆放排列配置,包含多個佈局元素的複雜規則。swift
LayoutSpecs
包含 LayoutElements
,並對 LayoutElements
進行整理。緩存
全部的 ASDisplayNode
和 ASLayoutSpec
都遵照 <ASLayoutElement>
協議,這意味着你能夠經過兩個 Nodes
和其餘的 LayoutSpecs
,生成或者組合一個新的 LayoutSpecs
。bash
<ASLayoutElement>
協議有一些屬性用於建立很是複雜的佈局。 另外,LayoutSpecs
也有本身的一組屬性,能夠調整佈局元素的排列。 服務器
在這裏,你能夠看到黃色突出顯示的 ASTextNodes
,頂部圖像 ASVideoNode
和盒子佈局規則 ASStackLayoutSpec
是如何組合並建立了一個複雜界面。數據結構
使用中心佈局規則 ASCenterLayoutSpec
和覆蓋佈局規則 ASOverlayLayoutSpec
,來放置頂部圖像 ASVideoNode
中的播放按鈕。
根據元素的即時可用內容,它們有一個固有大小,好比,ASTextNode
能夠根據 .string
屬性,肯定自身的 size,其餘具備固有大小的 Node
有:
ASImageNode
ASTextNode
ASButtonNode
全部其餘的 Node
在加載外部資源以前,或者沒有固有大小,或者缺乏一個固有大小。例如,在從 URL 下載圖像以前,ASNetworkImageNode
並不能肯定它的大小,這些元素包括:
ASVideoNode
ASVideoPlayerNode
ASNetworkImageNode
ASEditableTextNode
缺乏初始固有大小的這些 Node
必須使用 ASRatioLayoutSpec(比例佈局規則)
、ASAbsoluteLayoutSpec(絕對佈局規則)
或樣式對象的 .size
屬性爲它們設置初始大小。
在任何 ASDisplayNode
或 ASLayoutSpec
上調用 -asciiArtString
都會返回該對象及其子項的字符圖。 你也能夠在任何 Node
或 layoutSpec
中設置 .debugName
,這樣也將包含字符圖,下面是一個示例:
-----------------------ASStackLayoutSpec----------------------
| -----ASStackLayoutSpec----- -----ASStackLayoutSpec----- |
| | ASImageNode | | ASImageNode | |
| | ASImageNode | | ASImageNode | |
| --------------------------- --------------------------- |
--------------------------------------------------------------複製代碼
你還能夠在任何 ASLayoutElement
,好比 Node
和 layoutSpec
上打印樣式對象,這在調試 .size
屬性時特別有用。
(lldb) po _photoImageNode.style
Layout Size = min {414pt, 414pt} <= preferred {20%, 50%} <= max {414pt, 414pt}複製代碼
點擊查看layoutSpec
的示例工程。
爲了建立這個一個佈局,咱們將使用:
ASStackLayoutSpec
;ASStackLayoutSpec
;ASInsetLayoutSpec
;下圖展現了一個由 Node
和 LayoutSpecs
組成的佈局元素:
class TZYVC: ASViewController<ASDisplayNode> {
init() {
let node = TZYNode()
super.init(node: node)
}
override func viewDidLoad() {
super.viewDidLoad()
node.backgroundColor = UIColor.red
}
}
///////////////////////////////////////////////////
class TZYNode: ASDisplayNode {
// 圖中的 san fran ca
lazy var postLocationNode: ASTextNode = {
return ASTextNode()
}()
// 圖中的 hannahmbanana
lazy var userNameNode: ASTextNode = {
return ASTextNode()
}()
// 圖中的 30m
lazy var postTimeNode: ASTextNode = {
return ASTextNode()
}()
override init() {
super.init()
self.postLocationNode.attributedText = NSAttributedString(string: "san fran ca")
self.userNameNode.attributedText = NSAttributedString(string: "hannahmbanana")
self.postTimeNode.attributedText = NSAttributedString(string: "30m")
addSubnode(postLocationNode)
addSubnode(userNameNode)
addSubnode(postTimeNode)
postTimeNode.backgroundColor = .brown
userNameNode.backgroundColor = .cyan
postLocationNode.backgroundColor = .green
}
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
// 聲明一個垂直排列的盒子
let nameLoctionStack = ASStackLayoutSpec.vertical()
// 定義了項目的縮小比例,默認爲 1,即若是空間不足,該項目將縮小
// 如全部元素都爲 1,空間不足時,全部元素等比例縮放
// 如其中一個是 0,則此元素不縮放,其餘元素均分剩餘空間
nameLoctionStack.style.flexShrink = 1.0
// 定義元素的放大比例,默認爲 0,即若是存在剩餘空間,也不放大
// 如全部元素都爲 1,均分剩餘空間
// 如其中一個爲 2,那麼這個元素佔據的空間是其餘元素的一倍
nameLoctionStack.style.flexGrow = 1.0
// 根據定位地址 node 是否賦值,肯定是否將其加入視圖
if postLocationNode.attributedText != nil {
nameLoctionStack.children = [userNameNode, postLocationNode]
}
else {
nameLoctionStack.children = [userNameNode]
}
// 聲明一個水平排列的盒子
// direction: .horizontal 主軸是水平的
// spacing: 40 其子元素的間距是 40
// justifyContent: .start 在主軸上從左至右排列
// alignItems: .center 在次軸也就是垂直軸中居中
// children: [nameLoctionStack, postTimeNode] 包含的子元素
let headerStackSpec = ASStackLayoutSpec(direction: .horizontal,
spacing: 40,
justifyContent: .start,
alignItems: .center,
children: [nameLoctionStack, postTimeNode])
// 插入佈局規則
return ASInsetLayoutSpec(insets: UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10), child: headerStackSpec)
}
}複製代碼
將示例項目從縱向轉換爲橫向,查看間隔是如何增加和收縮的。
要建立這個佈局,咱們將使用:
ASInsetLayoutSpec
;ASOverlayLayoutSpec
;override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
let photoDimension: CGFloat = constrainedSize.max.width / 4.0
photoNode.style.preferredSize = CGSize(width: photoDimension, height: photoDimension)
// CGFloat.infinity 設定 titleNode 上邊距無限大
let insets = UIEdgeInsets(top: CGFloat.infinity, left: 12, bottom: 12, right: 12)
let textInsetSpec = ASInsetLayoutSpec(insets: insets, child: titleNode)
return ASOverlayLayoutSpec(child: photoNode, overlay: textInsetSpec)
}複製代碼
要建立這個佈局,咱們將用到:
size
和 position
的 ASLayoutable
屬性;ASAbsoluteLayoutSpec
;override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
iconNode.style.preferredSize = CGSize(width: 40, height: 40);
iconNode.style.layoutPosition = CGPoint(x: 150, y: 0);
photoNode.style.preferredSize = CGSize(width: 150, height: 150);
photoNode.style.layoutPosition = CGPoint(x: 40 / 2.0, y: 40 / 2.0);
let absoluteSpec = ASAbsoluteLayoutSpec(children: [photoNode, iconNode])
// ASAbsoluteLayoutSpec 的 sizing 屬性從新建立了 Texture Layout API 1.0 中的 ASStaticLayoutSpec
absoluteSpec.sizing = .sizeToFit
return absoluteSpec;
}複製代碼
要建立一個相似 Pinterest 搜索視圖的單一單元格佈局,咱們將用到:
ASInsetLayoutSpec
;ASCenterLayoutSpec
;override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
let insets = UIEdgeInsets(top: 0, left: 12, bottom: 4, right: 4)
let inset = ASInsetLayoutSpec(insets: insets, child: _titleNode)
return ASCenterLayoutSpec(centeringOptions: .Y, sizingOptions: .minimumX, child: inset)
}複製代碼
建立一個如上的佈局,咱們須要用到:
ASInsetLayoutSpec
;ASStackLayoutSpec
;下圖展現了一個 layoutables
是如何經過 layoutSpecs
和 Node
組成的:
如下的代碼也能夠在 ASLayoutSpecPlayground
這個示例項目中找到。
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
topSeparator.style.flexGrow = 1.0
bottomSeparator.style.flexGrow = 1.0
textNode.style.alignSelf = .center
let verticalStackSpec = ASStackLayoutSpec.vertical()
verticalStackSpec.spacing = 20
verticalStackSpec.justifyContent = .center
verticalStackSpec.children = [topSeparator, textNode, bottomSeparator]
return ASInsetLayoutSpec(insets:UIEdgeInsets(top: 60, left: 0, bottom: 60, right: 0), child: verticalStackSpec)
}複製代碼
如下的 ASLayoutSpec
子類能夠用來組成簡單或者很是複雜的佈局:
規則 | 描述 |
---|---|
ASWrapperLayoutSpec | 填充佈局 |
ASStackLayoutSpec | 盒子佈局 |
ASInsetLayoutSpec | 插入佈局 |
ASOverlayLayoutSpec | 覆蓋佈局 |
ASBackgroundLayoutSpec | 背景佈局 |
ASCenterLayoutSpec | 中心佈局 |
ASRatioLayoutSpec | 比例佈局 |
ASRelativeLayoutSpec | 頂點佈局 |
ASAbsoluteLayoutSpec | 絕對佈局 |
你也能夠建立一個 ASLayoutSpec
的子類以製做本身的佈局規則。
ASWrapperLayoutSpec
是一個簡單的 ASLayoutSpec
子類,它能夠封裝了一個 LayoutElement
,並根據 LayoutElement
上設置的大小計算其佈局及子元素佈局。
ASWrapperLayoutSpec
能夠輕鬆的從 -layoutSpecThatFits:
中返回一個 subnode
。 你能夠在這個 subnode
上設定 size
,可是若是你須要設定 .position
,請使用 ASAbsoluteLayoutSpec
。
// 返回一個 subnode
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
return ASWrapperLayoutSpec(layoutElement: _subnode)
}
// 設定 size,但不包括 position。
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
_subnode.style.preferredSize = CGSize(width: constrainedSize.max.width,
height: constrainedSize.max.height / 2.0)
return ASWrapperLayoutSpec(layoutElement: _subnode)
}複製代碼
在 Texture 中的全部 layoutSpec
中,ASStackLayoutSpec
是最有用的,也是最強大的。 ASStackLayoutSpec
使用 flexbox
來肯定其子元素的 size
和 position
。 Flexbox 旨在爲不一樣的屏幕尺寸提供一致的佈局, 在盒子佈局中,你垂直或水平的對其元素。 盒子佈局也能夠是另外一個盒子的子佈局,這使得盒子佈局規則幾乎能夠勝任任何的佈局。
除了 ASLayoutElement
屬性,ASStackLayoutSpec
還有 7 個屬性:
direction
指定子元素的排序方向,若是設置了 horizontalAlignment
和 verticalAlignment
,它們將被再次解析,這會致使 justifyContent
和 alignItems
也會相應地更新。
spacing
描述子元素之間的距離
horizontalAlignment
指定子元素如何在水平方向上對齊,它的實際效果取決於 direction
,設置對齊會使 justifyContent
或 alignItems
更新。在 direction
改變以後,對齊方式仍然有效,所以,這是一個優先級高的屬性。
verticalAlignment
指定子元素如何在垂直方向上對齊,它的實際效果取決於 direction
,設置對齊會使 justifyContent
或 alignItems
更新。在 direction
改變以後,對齊方式仍然有效,所以,這是一個優先級高的屬性。
justifyContent
描述子元素之間的距離。
alignItems
描述子元素在十字軸上的方向。
spacing 和 justifyContent 原文都是 The amount of space between each child.
spacing 以個人理解應該翻譯的沒錯,可是 justifyContent 感受不太準確,這幾個屬性讀者能夠查閱 CSS 文檔自行理解。
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
let mainStack = ASStackLayoutSpec(direction: .horizontal,
spacing: 6.0,
justifyContent: .start,
alignItems: .center,
children: [titleNode, subtitleNode])
// 設置盒子約束大小
mainStack.style.minWidth = ASDimensionMakeWithPoints(60.0)
mainStack.style.maxHeight = ASDimensionMakeWithPoints(40.0)
return mainStack
}複製代碼
Flexbox 在 Web 上的工做方式與在 CSS 中的工做方式相同,單有一些例外。例如,默認值是不一樣的,沒有 flex
參數,有關更多信息,請參閱 Web Flexbox 差別。
在佈局過程當中,ASInsetLayoutSpec
將其 constrainedSize.max
減去其 insets 的 CGSize
傳遞給它的子節點, 一旦子節點肯定了它的 size
,insetSpec
將它的最終 size
做爲子節點的 size
和 margin
。
因爲 ASInsetLayoutSpec
是根據其子節點的 size
來肯定的,所以子節點必須具備固有大小或明確設置其 size
。
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
...
let insets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
let headerWithInset = ASInsetLayoutSpec(insets: insets, child: textNode)
...
}複製代碼
若是在你將 UIEdgeInsets
中的一個值設置爲 INFINITY,則 insetSpec
將只使用子節點的固有大小,請看 圖像上覆蓋文本
這個例子。
ASOverlayLayoutSpec
將其上面的子節點(紅色)延伸,覆蓋一個子節點(藍色)。
overlaySpec
的 size
根據子節點的 size
計算, 在下圖中,子節點是藍色的層,而後將子節點的 size
做爲 constrainedSize
傳遞給疊加布局元素(紅色), 所以,重要的一點是,子節點(藍色)必須具備固有大小或明確設置 size
。
當使用 ASOverlayLayoutSpec
進行自動的子節點管理時,節點有時會表現出錯誤的順序,這是一個已知的問題,而且很快就會解決。當前的解決方法是手動添加節點,佈局元素(紅色)必須做爲子節點添加到父節點後面的子節點(藍色)。
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
let backgroundNode = ASDisplayNodeWithBackgroundColor(UIColor.blue)
let foregroundNode = ASDisplayNodeWithBackgroundColor(UIColor.red)
return ASOverlayLayoutSpec(child: backgroundNode, overlay: foregroundNode)
}複製代碼
ASBackgroundLayoutSpec
設置一個子節點(藍色)爲內容,將背後的另外一個子節點拉伸爲背景(紅色)。
ASBackgroundLayoutSpec
的 size
根據子節點的 size
肯定,在下圖中,子節點是藍色層,子節點的 size
做爲 constrainedSize
傳遞給背景圖層(紅色),所以重要的一點是,子節點(藍色)必須有一個固有大小或明確設置 size
。
當使用 ASOverlayLayoutSpec
進行自動的子節點管理時,節點有時會表現出錯誤的順序,這是一個已知的問題,而且很快就會解決。當前的解決方法是手動添加節點,佈局元素(藍色)必須做爲子節點添加到父節點後面的子節點(紅色)。
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
let backgroundNode = ASDisplayNodeWithBackgroundColor(UIColor.red)
let foregroundNode = ASDisplayNodeWithBackgroundColor(UIColor.blue)
return ASBackgroundLayoutSpec(child: foregroundNode, background: backgroundNode)
}複製代碼
注意:添加子節點的順序對於這個佈局規則是很重要的。 背景對象必須在前臺對象以前做爲子節點添加到父節點,目前使用 ASM 不能保證這個順序必定是正確的!
ASCenterLayoutSpec
將其子節點的中心設置爲最大的 constrainedSize
的中心。
若是 ASCenterLayoutSpec
的寬度或高度沒有設定約束,那麼它會縮放到和子節點的寬度或高度一致。
ASCenterLayoutSpec
有兩個屬性:
centeringOptions:
決定子節點如何在 ASCenterLayoutSpec
中居中,可選值包括:None,X,Y,XY。
sizingOptions:
決定 ASCenterLayoutSpec
佔用多少空間,可選值包括:Default,minimum X,minimum Y,minimum XY。
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
let subnode = ASDisplayNodeWithBackgroundColor(UIColor.green, CGSize(width: 60.0, height: 100.0))
let centerSpec = ASCenterLayoutSpec(centeringOptions: .XY, sizingOptions: [], child: subnode)
return centerSpec
}複製代碼
ASRatioLayoutSpec
能夠以固定的寬高比來縮放子節點。 這個規則必須將一個寬度或高度傳遞給它做爲一個 constrainedSize
,由於它使用這個值來進行計算。
使用 ASRatioLayoutSpec
爲 ASNetworkImageNode
或 ASVideoNode
提供固有大小是很是常見的,由於二者在內容從服務器返回以前都沒有固有大小。
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
// 將 subnode 縮放一半
let subnode = ASDisplayNodeWithBackgroundColor(UIColor.green, CGSize(width: 100, height: 100.0))
let ratioSpec = ASRatioLayoutSpec(ratio: 0.5, child: subnode)
return ratioSpec
}複製代碼
根據水平位置和垂直位置的設定,將一個子節點放置在九宮格佈局規則中的任意位置。
這是一個很是強大的佈局規則,可是它很是複雜,在這個概述中沒法逐一闡述, 有關更多信息,請參閱 ASRelativeLayoutSpec
的 -calculateLayoutThatFits:
方法和屬性。
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
...
let backgroundNode = ASDisplayNodeWithBackgroundColor(UIColor.blue)
let foregroundNode = ASDisplayNodeWithBackgroundColor(UIColor.red, CGSize(width: 70.0, height: 100.0))
let relativeSpec = ASRelativeLayoutSpec(horizontalPosition: .start,
verticalPosition: .start,
sizingOption: [],
child: foregroundNode)
let backgroundSpec = ASBackgroundLayoutSpec(child: relativeSpec, background: backgroundNode)
...
}複製代碼
ASAbsoluteLayoutSpec
中你能夠經過設置它們的 layoutPosition
屬性來指定其子節點的橫縱座標。 絕對佈局比其餘類型的佈局相比,不太靈活且難以維護。
ASAbsoluteLayoutSpec
有一個屬性:
sizing:
肯定 ASAbsoluteLayoutSpec
將佔用多少空間,可選值包括:Default,Size to Fit。請注意,Size to Fit 將複製舊的 ASStaticLayoutSpec
的行爲。
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
let maxConstrainedSize = constrainedSize.max
// 在一個靜態佈局中,使用 ASAbsoluteLayoutSpec 佈局全部子節點
guitarVideoNode.style.layoutPosition = CGPoint.zero
guitarVideoNode.style.preferredSize = CGSize(width: maxConstrainedSize.width, height: maxConstrainedSize.height / 3.0)
nicCageVideoNode.style.layoutPosition = CGPoint(x: maxConstrainedSize.width / 2.0, y: maxConstrainedSize.height / 3.0)
nicCageVideoNode.style.preferredSize = CGSize(width: maxConstrainedSize.width / 2.0, height: maxConstrainedSize.height / 3.0)
simonVideoNode.style.layoutPosition = CGPoint(x: 0.0, y: maxConstrainedSize.height - (maxConstrainedSize.height / 3.0))
simonVideoNode.style.preferredSize = CGSize(width: maxConstrainedSize.width / 2.0, height: maxConstrainedSize.height / 3.0)
hlsVideoNode.style.layoutPosition = CGPoint(x: 0.0, y: maxConstrainedSize.height / 3.0)
hlsVideoNode.style.preferredSize = CGSize(width: maxConstrainedSize.width / 2.0, height: maxConstrainedSize.height / 3.0)
return ASAbsoluteLayoutSpec(children: [guitarVideoNode, nicCageVideoNode, simonVideoNode, hlsVideoNode])
}複製代碼
ASLayoutSpec
是全部佈局規則的父類,它的主要工做是處理和管理全部的子類,它也能夠用來建立自定義的佈局規則。不過建立 ASLayoutSpec
的自定義子類是一項 super advanced
級別的操做,若是你有這方面的須要,建議你嘗試將咱們提供的佈局規則進行組合,以建立更高級的佈局。
ASLayoutSpec
的另外一個用途是應用了 .flexShrink
或者 .flexGrow
是,在 ASStackLayoutSpec
中做爲一個 spacer
和其餘子節點一塊兒使用,
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
...
let spacer = ASLayoutSpec()
spacer.style.flexGrow = 1.0
stack.children = [imageNode, spacer, textNode]
...
}複製代碼
subnode
或 layoutSpec
中生效;subnode
或 layoutSpec
中生效;Node
和 layoutSpec
;請注意,如下屬性只有在 ASStackLayout
的 subnode
上設置纔會生效。
CGFloat
類型,direction 上與前一個 node 的間隔。
CGFloat
類型,direction 上與後一個 node 的間隔。
Bool
類型,子節點尺寸總和小於 minimum ,即存在剩餘空間時,是否放大。
Bool
類型,子節點總和大於 maximum,即空間不足時,是否縮小。
ASDimension
類型,描述在剩餘空間是均分的狀況下,應用 flexGrow
或 flexShrink
屬性以前,該對象在盒子中垂直或水平方向的初始 size
,
ASStackLayoutAlignSelf
類型,描述對象在十字軸的方向,此屬性會覆蓋 alignItems
,可選值有:
ASStackLayoutAlignSelfAuto
ASStackLayoutAlignSelfStart
ASStackLayoutAlignSelfEnd
ASStackLayoutAlignSelfCenter
ASStackLayoutAlignSelfStretch
CGFloat
類型,用於基線對齊,描述對象從頂部到其基線的距離。
CGFloat
類型,用於基線對齊,描述對象從基線到其底部的距離。
請注意,如下屬性只有在 AbsoluteLayout
的 subnode
上設置纔會生效。
CGPoint
類型,描述該對象在 ASAbsoluteLayoutSpec
父規則中的位置。
請注意,如下屬性適用於全部佈局元素。
ASDimension
類型,width
屬性描述了 ASLayoutElement
內容區域的寬度。 minWidth
和 maxWidth
屬性會覆蓋 width
, 默認值爲 ASDimensionAuto
。
ASDimension
類型,height
屬性描述了 ASLayoutElement
內容區域的高度。 minHeight
和 maxHeight
屬性會覆蓋 height
,默認值爲 ASDimensionAuto
。
ASDimension
類型,minWidth
屬性用於設置一個特定佈局元素的最小寬度。 它能夠防止 width
屬性的使用值小於 minWidth
指定的值,minWidth
的值會覆蓋 maxWidth
和 width
。 默認值爲 ASDimensionAuto
。
ASDimension
類型,maxWidth
屬性用於設置一個特定佈局元素的最大寬度。 它能夠防止 width
屬性的使用值大於 maxWidth
指定的值,maxWidth
的值會覆蓋 width
,minWidth
會覆蓋 maxWidth
。 默認值爲 ASDimensionAuto
。
ASDimension
類型,minHeight
屬性用於設置一個特定佈局元素的最小高度。 它能夠防止 height
屬性的使用值小於 minHeight
指定的值。 minHeight
的值會覆蓋 maxHeight
和 height
。 默認值爲 ASDimensionAuto
。
ASDimension
類型,maxHeight
屬性用於設置一個特定佈局元素的最大高度,它能夠防止 height
屬性的使用值大於 maxHeight
指定的值。 maxHeight
的值會覆蓋 height
,minHeight
會覆蓋 maxHeight
。 默認值爲 ASDimensionAuto
。
CGSize
類型, 建議佈局元素的 size
應該是多少。 若是提供了 minSize
或 maxSize
,而且 preferredSize
超過了這些值,則強制使用 minSize
或 maxSize
。 若是未提供 preferredSize
,則佈局元素的 size
默認爲 calculateSizeThatFits:
方法提供的固有大小。
此方法是可選的,可是對於沒有固有大小或須要用與固有大小不一樣的的 size 進行佈局的節點,則必須指定 preferredSize
或 preferredLayoutSize
中的一個,好比沒這個屬性能夠在 ASImageNode
上設置,使這個節點的 size
和圖片 size
不一樣。
警告:當 size 的寬度或高度是相對值時調用 getter 將進行斷言。
CGSize
類型,可選屬性,爲佈局元素提供最小尺寸,若是提供,minSize
將會強制使用。 若是父級佈局元素的 minSize
小於其子級的 minSize
,則強制使用子級的 minSize
,而且其大小將擴展到佈局規則以外。
例如,若是給全屏容器中的某個元素設置 50% 的 preferredSize
相對寬度,和 200pt 的 minSize
寬度,preferredSize
會在 iPhone 屏幕上產生 160pt 的寬度,但因爲 160pt 低於 200pt 的 minSize
寬度,所以最終該元素的寬度會是 200pt。
CGSize
類型,可選屬性,爲佈局元素提供最大尺寸,若是提供,maxSize
將會強制使用。 若是子佈局元素的 maxSize
小於其父級的 maxSize
,則強制使用子級的 maxSize
,而且其大小將擴展到佈局規則以外。
例如,若是給全屏容器中的某個元素設置 50% 的 preferredSize
相對寬度,和 120pt 的 maxSize
寬度,preferredSize
會在 iPhone 屏幕上產生 160pt 的寬度,但因爲 160pt 高於 120pt 的 maxSize
寬度,所以最終該元素的寬度會是 120pt。
ASLayoutSize
類型,爲佈局元素提供建議的相對 size
。 ASLayoutSize
使用百分比而不是點來指定佈局。 例如,子佈局元素的寬度應該是父寬度的 50%。 若是提供了可選的 minLayoutSize
或 maxLayoutSize
,而且 preferredLayoutSize
超過了這些值,則將使用 minLayoutSize
或 maxLayoutSize
。 若是未提供此可選值,則佈局元素的 size
將默認是 calculateSizeThatFits:
提供的固有大小。
ASLayoutSize
類型, 可選屬性,爲佈局元素提供最小的相對尺寸, 若是提供,minLayoutSize
將會強制使用。 若是父級佈局元素的 minLayoutSize
小於其子級的 minLayoutSize
,則會強制使用子級的 minLayoutSize
,而且其大小將擴展到佈局規則以外。
ASLayoutSize
類型, 可選屬性,爲佈局元素提供最大的相對尺寸。 若是提供,maxLayoutSize
將會強制使用。 若是父級佈局元素的 maxLayoutSize
小於其子級的 maxLayoutSize
,那麼將強制使用子級的 maxLayoutSize
,而且其大小將擴展到佈局規則以外。
理解 Layout API 的各類類型最簡單方法是查看全部單位之間的相互關係。
ASDimension
基本上是一個正常的 CGFloat
,支持表示一個 pt 值,一個相對百分比值或一個自動值,這個單位容許一個的 API 同時使用固定值和相對值。
// 返回一個相對值
ASDimensionMake("50%")
ASDimensionMakeWithFraction(0.5)
// 返回一個 pt 值
ASDimensionMake("70pt")
ASDimensionMake(70)
ASDimensionMakeWithPoints(70)複製代碼
使用 ASDimension
的示例:
ASDimension
用於設置 ASStackLayoutSpec
子元素的 flexBasis
屬性。 flexBasis
屬性根據在盒子排序方向是水平仍是垂直,來指定對象的初始大小。在下面的視圖中,咱們但願左邊的盒子佔據水平寬度的 40%,右邊的盒子佔據寬度的 60%,這個效果咱們能夠經過在水平盒子容器的兩個 childen
上設置 .flexBasis
屬性來實現:
self.leftStack.style.flexBasis = ASDimensionMake("40%")
self.rightStack.style.flexBasis = ASDimensionMake("60%")
horizontalStack.children = [self.leftStack, self.rightStack]]複製代碼
ASLayoutSize
相似於 CGSize
,可是它的寬度和高度能夠同時使用 pt 值或百分比值。 寬度和高度的類型是獨立的,它們的值類型能夠不一樣。
ASLayoutSizeMake(_ width: ASDimension, _ height: ASDimension)複製代碼
ASLayoutSize
用於描述佈局元素的 .preferredLayoutSize
,.minLayoutSize
和 .maxLayoutSize
屬性,它容許在一個 API 中同時使用固定值和相對值。
ASDimension
類型 auto
表示佈局元素能夠根據狀況選擇最合理的方式。
let width = ASDimensionMake(.auto, 0)
let height = ASDimensionMake("50%")
layoutElement.style.preferredLayoutSize = ASLayoutSizeMake(width, height)複製代碼
你也可使用固定值設置佈局元素的 .preferredSize
、.minSize
、.maxSize
屬性。
layoutElement.style.preferredSize = CGSize(width: 30, height: 60)複製代碼
大多數狀況下,你不須要要限制寬度和高度。若是你須要,可使用 ASDimension
值單獨設置佈局元素的 size
屬性:
layoutElement.style.width = ASDimensionMake("50%")
layoutElement.style.minWidth = ASDimensionMake("50%")
layoutElement.style.maxWidth = ASDimensionMake("50%")
layoutElement.style.height = ASDimensionMake("50%")
layoutElement.style.minHeight = ASDimensionMake("50%")
layoutElement.style.maxHeight = ASDimensionMake("50%")複製代碼
UIKit
沒有提供一個機制綁定最小和最大的 CGSize
,所以,爲了支持最小和最大的 CGSize
,咱們建立了 ASSizeRange
, ASSizeRange
主要應用在 Llayout API 的內部,可是 layoutSpecThatFits:
方法的的輸入參數 constrainedSize
是 ASSizeRange
類型。
func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec複製代碼
傳遞給 ASDisplayNode
子類 layoutSpecThatFits:
方法的 constrainedSize
是 Node
最適合的最小和最大尺寸,你能夠在佈局元素上使用 constrainedSize
中包含的最小和最大 CGSize
。
Layout Transition API 旨在讓全部的 Texture 動畫都變得簡單 - 甚至能夠將一個視圖集轉爲另外一個徹底不一樣的視圖集!
使用這個系統,你只需指定所需的佈局,Texture 會根據當前的佈局自動找出差別,它會自動添加新的元素,動畫結束後自動刪除不須要的元素,並更新現有的元素的位置。
同時也有很是容易使用的 API,讓你能夠徹底自定義一個新元素的起始位置,以及移除元素的結束位置。
使用 Layout Transition API 必須使用自動子節點管理功能。
Layout Transition API 使得在使用 node
製做的佈局中,在 node
的內部狀態更改時,能夠很容易地進行動畫操做。
想象一下,你但願實現這個註冊的表單,而且在點擊 Next
時出現新的輸入框的動畫:
實現這一點的標準方法是建立一個名爲 SignupNode
的容器節點,SignupNode
包含兩個可編輯的 text field node
和一個 button node
做爲子節點。 咱們將在 SignupNode
上包含一個名爲 fieldState
的屬性,該屬性用於當計算佈局時,要顯示哪一個 text field node
。
SignupNode
容器的內部 layoutSpec
看起來是這樣的:
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
let fieldNode: FieldNode
if self.fieldState == .signupNodeName {
fieldNode = self.nameField
} else {
fieldNode = self.ageField
}
let stack = ASStackLayoutSpec()
stack.children = [fieldNode, buttonNode]
let insets = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
return ASInsetLayoutSpec(insets: insets, child: stack)
}複製代碼
爲了在本例中觸發從 nameField
到 ageField
的轉換,咱們將更新 SignupNode
的 .fieldState
屬性,並使用 transitionLayoutWithAnimation
方法觸發動畫。
這個方法將使當前計算的佈局失效,並從新計算 ageField
在盒子中的佈局。
self.signupNode.fieldState = .signupNodeName
self.signupNode.transitionLayout(withAnimation: true, shouldMeasureAsync: true)複製代碼
在這個 API 的默認實現中,佈局將從新計算,並使用它的 sublayouts
來對 SignupNode
子節點的 size
和 position
進行設置,但沒有動畫。這個 API 的將來版本極可能會包括佈局之間的默認動畫,有關你但願在此處看到的內容,咱們歡迎你進行反饋,可是,如今咱們須要實現一個自定義動畫塊來處理這個動畫。
下面的示例表示在 SignupNode
中的 animateLayoutTransition:
的重寫。
這個方法在經過 transitionLayoutWithAnimation:
計算出新佈局以後調用,在實現中,咱們將根據動畫觸發前設置的 fieldState
屬性執行特定的動畫。
override func animateLayoutTransition(_ context: ASContextTransitioning) {
if fieldState == .signupNodeName {
let initialNameFrame = context.initialFrame(for: ageField)
nameField.frame = initialNameFrame
nameField.alpha = 0
var finalAgeFrame = context.finalFrame(for: nameField)
finalAgeFrame.origin.x -= finalAgeFrame.size.width
UIView.animate(withDuration: 0.4, animations: {
self.nameField.frame = context.finalFrame(for: self.nameField)
self.nameField.alpha = 1
self.ageField.frame = finalAgeFrame
self.ageField.alpha = 0
}, completion: { finished in
context.completeTransition(finished)
})
} else {
var initialAgeFrame = context.initialFrame(for: nameField)
initialAgeFrame.origin.x += initialAgeFrame.size.width
ageField.frame = initialAgeFrame
ageField.alpha = 0
var finalNameFrame = context.finalFrame(for: ageField)
finalNameFrame.origin.x -= finalNameFrame.size.width
UIView.animate(withDuration: 0.4, animations: {
self.ageField.frame = context.finalFrame(for: self.ageField)
self.ageField.alpha = 1
self.nameField.frame = finalNameFrame
self.nameField.alpha = 0
}, completion: { finished in
context.completeTransition(finished)
})
}
}複製代碼
此方法中傳遞的 ASContextTransitioning
上下文對象包含相關信息,能夠幫助你肯定轉換先後的節點狀態。它包括新舊約束大小,插入和刪除的節點,甚至是新舊 ASLayout
原始對象。在 SignupNode
示例中,咱們使用它來肯定每一個節點的 frame
並在一個地方讓它們進動畫。
一旦動畫完成,就必須調用上下文對象的 completeTransition:
,由於它將爲新佈局內部執行必要的步驟,以使新佈局生效。
請注意,在這個過程當中沒有使用 addSubnode:
或 removeFromSupernode:
。 Layout Transition API 會分析舊佈局和新佈局之間節點層次結構的差別,經過自動子節點管理隱式的執行節點插入和刪除。
在執行 animateLayoutTransition:
以前插入節點,這是在開始動畫以前手動管理層次結構的好地方。在上下文對象執行 completeTransition :
以後,清除將在 didCompleteLayoutTransition:
中執行。
若是你須要手動執行刪除,請重寫 didCompleteLayoutTransition:
並執行自定義的操做。須要注意的是,這樣作會覆蓋默認刪除行爲,建議你調用 super
或遍歷上下文對象中的 removedSubnodes
來執行清理。
將 NO
傳遞給 transitionLayoutWithAnimation:
將貫穿 animateLayoutTransition:
和 didCompleteLayoutTransition:
的執行,並將 [context isAnimated]
屬性設置爲 NO
。如何處理這樣的狀況取決於你的選擇 - 若是有的話。提供默認實現的一種簡單方法是調用 super
:
override func animateLayoutTransition(_ context: ASContextTransitioning) {
if context.isAnimated() {
} else {
super.animateLayoutTransition(context)
}
}複製代碼
有些時候,你只想對節點的 bounds
變化做出響應,從新計算其佈局。這種狀況,能夠在節點上調用 transitionLayoutWithSizeRange:animated:
。
該方法相似於 transitionLayoutWithAnimation:
,可是若是傳遞的 ASSizeRange
等於當前的 constrainedSizeForCalculatedLayout
,則不會觸發動畫。 這在響應旋轉事件和控制器 size
發生變化時很是有用。