[譯] AsyncDisplayKit/Texture 官方文檔(2)

官方文檔連接:texturegroup.org/docs/gettin…html

佈局快速入門

[譯] AsyncDisplayKit/Texture 官方文檔(1) node

開發初衷和優點

Layout API 的出現是爲了提供一種能夠替代 UIKit Auto Layout 的高性能方案,UIKit Auto Layout 在複雜的視圖結構中,計算量會呈指數級增加,Texture 的佈局方案相對 Auto Layout 有如下優勢:git

  • 快:Texture 的佈局計算和手寫 Frame 同樣的快;
  • 異步和併發:佈局能夠在後臺線程上計算,用戶交互不會所以而中斷;
  • 聲明式渲染:佈局使用不可變的數據結構聲明,這讓佈局代碼變得更容易開發、維護、調試、測試和評審;
  • 可緩存:若是佈局是不可變的,能夠在後臺預先計算並緩存,這可讓用戶感受更快;
  • 可擴展:在不一樣的類中使用相同的佈局會變得很方便;

靈感來自於 CSS Flexbox

熟悉 Flexbox 的人會注意到這兩個系統有許多的類似之處, 但 Layout API 並無從新實現全部的 CSS。github

基本概念

Texture 的佈局主要圍繞兩個概念展開:web

  1. 佈局規則
  2. 佈局元素

佈局規則/Layout Specs

佈局規則沒有物理存在,它經過充當 LayoutElements 的容器,理解多個 LayoutElements 之間的關聯,完成 LayoutElements 的位置排列。Texture 提供了 ASLayoutSpec 的幾個子類,涵蓋了從插入單個佈局元素的簡單規則,到能夠變化堆放排列配置,包含多個佈局元素的複雜規則。swift

佈局元素/Layout Elements

LayoutSpecs 包含 LayoutElements,並對 LayoutElements 進行整理。緩存

全部的 ASDisplayNodeASLayoutSpec 都遵照 <ASLayoutElement> 協議,這意味着你能夠經過兩個 Nodes 和其餘的 LayoutSpecs,生成或者組合一個新的 LayoutSpecsbash

<ASLayoutElement> 協議有一些屬性用於建立很是複雜的佈局。 另外,LayoutSpecs 也有本身的一組屬性,能夠調整佈局元素的排列。 服務器

結合佈局規則和佈局元素製做複雜界面

在這裏,你能夠看到黃色突出顯示的 ASTextNodes,頂部圖像 ASVideoNode 和盒子佈局規則 ASStackLayoutSpec 是如何組合並建立了一個複雜界面。數據結構

使用中心佈局規則 ASCenterLayoutSpec 和覆蓋佈局規則 ASOverlayLayoutSpec,來放置頂部圖像 ASVideoNode 中的播放按鈕。

一些 Node 須要設定 size

根據元素的即時可用內容,它們有一個固有大小,好比,ASTextNode 能夠根據 .string 屬性,肯定自身的 size,其餘具備固有大小的 Node 有:

  • ASImageNode
  • ASTextNode
  • ASButtonNode

全部其餘的 Node 在加載外部資源以前,或者沒有固有大小,或者缺乏一個固有大小。例如,在從 URL 下載圖像以前,ASNetworkImageNode 並不能肯定它的大小,這些元素包括:

  • ASVideoNode
  • ASVideoPlayerNode
  • ASNetworkImageNode
  • ASEditableTextNode

缺乏初始固有大小的這些 Node 必須使用 ASRatioLayoutSpec(比例佈局規則)ASAbsoluteLayoutSpec(絕對佈局規則) 或樣式對象的 .size 屬性爲它們設置初始大小。

佈局調試/Layout Debugging

在任何 ASDisplayNodeASLayoutSpec 上調用 -asciiArtString 都會返回該對象及其子項的字符圖。 你也能夠在任何 NodelayoutSpec 中設置 .debugName,這樣也將包含字符圖,下面是一個示例:

-----------------------ASStackLayoutSpec----------------------
|  -----ASStackLayoutSpec-----  -----ASStackLayoutSpec-----  |
|  |       ASImageNode       |  |       ASImageNode       |  |
|  |       ASImageNode       |  |       ASImageNode       |  |
|  ---------------------------  ---------------------------  |
--------------------------------------------------------------複製代碼

你還能夠在任何 ASLayoutElement ,好比 NodelayoutSpec 上打印樣式對象,這在調試 .size 屬性時特別有用。

(lldb) po _photoImageNode.style
Layout Size = min {414pt, 414pt} <= preferred {20%, 50%} <= max {414pt, 414pt}複製代碼

佈局示例

點擊查看layoutSpec示例工程

簡單的文本左對齊和右對齊

爲了建立這個一個佈局,咱們將使用:

  • 表示垂直的 ASStackLayoutSpec
  • 表示水平的 ASStackLayoutSpec
  • 插入標題的 ASInsetLayoutSpec

下圖展現了一個由 NodeLayoutSpecs 組成的佈局元素:

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)
}複製代碼

圖片上覆蓋圖標

要建立這個佈局,咱們將用到:

  • 設定 sizepositionASLayoutable 屬性;
  • 用於放置圖片和圖標的 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是如何經過 layoutSpecsNode 組成的:

如下的代碼也能夠在 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)
}複製代碼

佈局規則/Layout Specs

如下的 ASLayoutSpec 子類能夠用來組成簡單或者很是複雜的佈局:

規則 描述
ASWrapperLayoutSpec 填充佈局
ASStackLayoutSpec 盒子佈局
ASInsetLayoutSpec 插入佈局
ASOverlayLayoutSpec 覆蓋佈局
ASBackgroundLayoutSpec 背景佈局
ASCenterLayoutSpec 中心佈局
ASRatioLayoutSpec 比例佈局
ASRelativeLayoutSpec 頂點佈局
ASAbsoluteLayoutSpec 絕對佈局

你也能夠建立一個 ASLayoutSpec 的子類以製做本身的佈局規則。

ASWrapperLayoutSpec

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)
}複製代碼

ASStackLayoutSpec (Flexbox Container)

在 Texture 中的全部 layoutSpec 中,ASStackLayoutSpec 是最有用的,也是最強大的。 ASStackLayoutSpec 使用 flexbox 來肯定其子元素的 sizeposition 。 Flexbox 旨在爲不一樣的屏幕尺寸提供一致的佈局, 在盒子佈局中,你垂直或水平的對其元素。 盒子佈局也能夠是另外一個盒子的子佈局,這使得盒子佈局規則幾乎能夠勝任任何的佈局。

除了 ASLayoutElement 屬性,ASStackLayoutSpec 還有 7 個屬性:

  • direction

    指定子元素的排序方向,若是設置了 horizontalAlignmentverticalAlignment,它們將被再次解析,這會致使 justifyContentalignItems 也會相應地更新。

  • spacing

    描述子元素之間的距離

  • horizontalAlignment

    指定子元素如何在水平方向上對齊,它的實際效果取決於 direction,設置對齊會使 justifyContentalignItems 更新。在 direction 改變以後,對齊方式仍然有效,所以,這是一個優先級高的屬性。

  • verticalAlignment

    指定子元素如何在垂直方向上對齊,它的實際效果取決於 direction,設置對齊會使 justifyContentalignItems 更新。在 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

在佈局過程當中,ASInsetLayoutSpec 將其 constrainedSize.max 減去其 insets 的 CGSize 傳遞給它的子節點, 一旦子節點肯定了它的 sizeinsetSpec 將它的最終 size 做爲子節點的 sizemargin

因爲 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

ASOverlayLayoutSpec 將其上面的子節點(紅色)延伸,覆蓋一個子節點(藍色)。

overlaySpecsize 根據子節點的 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 設置一個子節點(藍色)爲內容,將背後的另外一個子節點拉伸爲背景(紅色)。

ASBackgroundLayoutSpecsize 根據子節點的 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

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

ASRatioLayoutSpec 能夠以固定的寬高比來縮放子節點。 這個規則必須將一個寬度或高度傳遞給它做爲一個 constrainedSize,由於它使用這個值來進行計算。

使用 ASRatioLayoutSpecASNetworkImageNodeASVideoNode 提供固有大小是很是常見的,由於二者在內容從服務器返回以前都沒有固有大小。

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

根據水平位置和垂直位置的設定,將一個子節點放置在九宮格佈局規則中的任意位置。

這是一個很是強大的佈局規則,可是它很是複雜,在這個概述中沒法逐一闡述, 有關更多信息,請參閱 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

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 是全部佈局規則的父類,它的主要工做是處理和管理全部的子類,它也能夠用來建立自定義的佈局規則。不過建立 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]
  ...
}複製代碼

佈局元素屬性/Layout Element Properties

  • ASStackLayoutElement Properties:只會在盒子佈局中的的 subnodelayoutSpec 中生效;
  • ASAbsoluteLayoutElement Properties:只會在絕對佈局中的的 subnodelayoutSpec 中生效;
  • ASLayoutElement Properties:適用於全部 NodelayoutSpec

ASStackLayoutElement Properties

請注意,如下屬性只有在 ASStackLayoutsubnode上設置纔會生效。

.style.spacingBefore

CGFloat 類型,direction 上與前一個 node 的間隔。

.style.spacingAfter

CGFloat 類型,direction 上與後一個 node 的間隔。

.style.flexGrow

Bool 類型,子節點尺寸總和小於 minimum ,即存在剩餘空間時,是否放大。

.style.flexShrink

Bool 類型,子節點總和大於 maximum,即空間不足時,是否縮小。

.style.flexBasis

ASDimension 類型,描述在剩餘空間是均分的狀況下,應用 flexGrowflexShrink 屬性以前,該對象在盒子中垂直或水平方向的初始 size

.style.alignSelf

ASStackLayoutAlignSelf 類型,描述對象在十字軸的方向,此屬性會覆蓋 alignItems,可選值有:

  • ASStackLayoutAlignSelfAuto
  • ASStackLayoutAlignSelfStart
  • ASStackLayoutAlignSelfEnd
  • ASStackLayoutAlignSelfCenter
  • ASStackLayoutAlignSelfStretch

.style.ascender

CGFloat 類型,用於基線對齊,描述對象從頂部到其基線的距離。

.style.descender

CGFloat 類型,用於基線對齊,描述對象從基線到其底部的距離。

ASAbsoluteLayoutElement Properties

請注意,如下屬性只有在 AbsoluteLayoutsubnode上設置纔會生效。

.style.layoutPosition

CGPoint 類型,描述該對象在 ASAbsoluteLayoutSpec 父規則中的位置。

ASLayoutElement Properties

請注意,如下屬性適用於全部佈局元素。

.style.width

ASDimension 類型,width 屬性描述了 ASLayoutElement 內容區域的寬度。 minWidthmaxWidth 屬性會覆蓋 width, 默認值爲 ASDimensionAuto

.style.height

ASDimension 類型,height 屬性描述了 ASLayoutElement 內容區域的高度。 minHeightmaxHeight 屬性會覆蓋 height,默認值爲 ASDimensionAuto

.style.minWidth

ASDimension 類型,minWidth 屬性用於設置一個特定佈局元素的最小寬度。 它能夠防止 width 屬性的使用值小於 minWidth 指定的值,minWidth 的值會覆蓋 maxWidthwidth。 默認值爲 ASDimensionAuto

.style.maxWidth

ASDimension 類型,maxWidth 屬性用於設置一個特定佈局元素的最大寬度。 它能夠防止 width 屬性的使用值大於 maxWidth 指定的值,maxWidth 的值會覆蓋 widthminWidth 會覆蓋 maxWidth。 默認值爲 ASDimensionAuto

.style.minHeight

ASDimension 類型,minHeight 屬性用於設置一個特定佈局元素的最小高度。 它能夠防止 height 屬性的使用值小於 minHeight 指定的值。 minHeight 的值會覆蓋 maxHeightheight。 默認值爲 ASDimensionAuto

.style.maxHeight

ASDimension 類型,maxHeight 屬性用於設置一個特定佈局元素的最大高度,它能夠防止 height 屬性的使用值大於 maxHeight 指定的值。 maxHeight 的值會覆蓋 heightminHeight 會覆蓋 maxHeight。 默認值爲 ASDimensionAuto

.style.preferredSize

CGSize 類型, 建議佈局元素的 size 應該是多少。 若是提供了 minSizemaxSize ,而且 preferredSize 超過了這些值,則強制使用 minSizemaxSize。 若是未提供 preferredSize,則佈局元素的 size 默認爲 calculateSizeThatFits: 方法提供的固有大小。

此方法是可選的,可是對於沒有固有大小或須要用與固有大小不一樣的的 size 進行佈局的節點,則必須指定 preferredSizepreferredLayoutSize 中的一個,好比沒這個屬性能夠在 ASImageNode 上設置,使這個節點的 size 和圖片 size 不一樣。

警告:當 size 的寬度或高度是相對值時調用 getter 將進行斷言。

.style.minSize

CGSize 類型,可選屬性,爲佈局元素提供最小尺寸,若是提供,minSize 將會強制使用。 若是父級佈局元素的 minSize 小於其子級的 minSize,則強制使用子級的 minSize,而且其大小將擴展到佈局規則以外。

例如,若是給全屏容器中的某個元素設置 50% 的 preferredSize 相對寬度,和 200pt 的 minSize 寬度,preferredSize 會在 iPhone 屏幕上產生 160pt 的寬度,但因爲 160pt 低於 200pt 的 minSize 寬度,所以最終該元素的寬度會是 200pt。

.style.maxSize

CGSize 類型,可選屬性,爲佈局元素提供最大尺寸,若是提供,maxSize 將會強制使用。 若是子佈局元素的 maxSize 小於其父級的 maxSize,則強制使用子級的 maxSize,而且其大小將擴展到佈局規則以外。

例如,若是給全屏容器中的某個元素設置 50% 的 preferredSize 相對寬度,和 120pt 的 maxSize 寬度,preferredSize 會在 iPhone 屏幕上產生 160pt 的寬度,但因爲 160pt 高於 120pt 的 maxSize 寬度,所以最終該元素的寬度會是 120pt。

.style.preferredLayoutSize

ASLayoutSize 類型,爲佈局元素提供建議的相對 sizeASLayoutSize 使用百分比而不是點來指定佈局。 例如,子佈局元素的寬度應該是父寬度的 50%。 若是提供了可選的 minLayoutSizemaxLayoutSize,而且 preferredLayoutSize 超過了這些值,則將使用 minLayoutSizemaxLayoutSize。 若是未提供此可選值,則佈局元素的 size 將默認是 calculateSizeThatFits: 提供的固有大小。

.style.minLayoutSize

ASLayoutSize 類型, 可選屬性,爲佈局元素提供最小的相對尺寸, 若是提供,minLayoutSize 將會強制使用。 若是父級佈局元素的 minLayoutSize 小於其子級的 minLayoutSize,則會強制使用子級的 minLayoutSize,而且其大小將擴展到佈局規則以外。

.style.maxLayoutSize

ASLayoutSize 類型, 可選屬性,爲佈局元素提供最大的相對尺寸。 若是提供,maxLayoutSize 將會強制使用。 若是父級佈局元素的 maxLayoutSize 小於其子級的 maxLayoutSize,那麼將強制使用子級的 maxLayoutSize,而且其大小將擴展到佈局規則以外。

Layout API Sizing

理解 Layout API 的各類類型最簡單方法是查看全部單位之間的相互關係。

ASDimension

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]]複製代碼

CGSize、ASLayoutSize

ASLayoutSize 相似於 CGSize,可是它的寬度和高度能夠同時使用 pt 值或百分比值。 寬度和高度的類型是獨立的,它們的值類型能夠不一樣。

ASLayoutSizeMake(_ width: ASDimension, _ height: ASDimension)複製代碼

ASLayoutSize 用於描述佈局元素的 .preferredLayoutSize.minLayoutSize.maxLayoutSize 屬性,它容許在一個 API 中同時使用固定值和相對值。

ASDimensionMake

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%")複製代碼

ASSizeRange

UIKit 沒有提供一個機制綁定最小和最大的 CGSize,所以,爲了支持最小和最大的 CGSize,咱們建立了 ASSizeRangeASSizeRange 主要應用在 Llayout API 的內部,可是 layoutSpecThatFits: 方法的的輸入參數 constrainedSizeASSizeRange 類型。

func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec複製代碼

傳遞給 ASDisplayNode 子類 layoutSpecThatFits: 方法的 constrainedSizeNode 最適合的最小和最大尺寸,你能夠在佈局元素上使用 constrainedSize 中包含的最小和最大 CGSize

Layout Transition API

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)
}複製代碼

爲了在本例中觸發從 nameFieldageField 的轉換,咱們將更新 SignupNode.fieldState 屬性,並使用 transitionLayoutWithAnimation 方法觸發動畫。

這個方法將使當前計算的佈局失效,並從新計算 ageField 在盒子中的佈局。

self.signupNode.fieldState = .signupNodeName
self.signupNode.transitionLayout(withAnimation: true, shouldMeasureAsync: true)複製代碼

在這個 API 的默認實現中,佈局將從新計算,並使用它的 sublayouts 來對 SignupNode 子節點的 sizeposition 進行設置,但沒有動畫。這個 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)
  }
}複製代碼

動畫 constrainedSize 更改

有些時候,你只想對節點的 bounds 變化做出響應,從新計算其佈局。這種狀況,能夠在節點上調用 transitionLayoutWithSizeRange:animated:

該方法相似於 transitionLayoutWithAnimation:,可是若是傳遞的 ASSizeRange 等於當前的 constrainedSizeForCalculatedLayout,則不會觸發動畫。 這在響應旋轉事件和控制器 size 發生變化時很是有用。

使用 Layout Transition API 的示例

未完待續

相關文章
相關標籤/搜索