Texture 布局 Layout

这是 Texture 文档系列翻译,其中结合了自己的理解和工作中的使用体会。如果哪里有误,希望指出。

  1. Texture 核心概念

  2. Texture 布局 Layout

  3. Texture 便捷方法

  4. Texture 性能优化

  5. Texture 容器 Node Containers

  6. Texture 基本控件 Node

  7. Texture 中 Node 的生命周期

简介

Texture 布局的优势

Layout API 是为了替代 UIKit Auto Layout 的高性能布局方案。Auto layout 在布局复杂视图时,成本成倍增加。Texture 的 Layout API 具有许多优点:

  • 快速:与手动布局一样快,比 auto layout 快很多。
  • 异步和并发:布局在后台线程计算,不影响用户交互。
  • 声明性(declarative):布局用不可变数据结构声明。这使布局代码更易于开发、文档编写、代码审查、测试、调试、配置和维护。
  • 可缓存:布局结果是不可变的数据结构,因此可以在后台对其预先计算并缓存,以提高用户感知性能。
  • 可扩展:易于在类之间共享代码。

受 CSS Flexbox 启发

如果你熟悉 Flexbox,会发现 Texture layout 和 Flexbox 有众多相似之处,然而 Texture Layout API 并未实现所有 CSS 功能。

基本概念

Texture 的布局系统围绕两个基本概念:

  1. 布局规范 Layout Specs。
  2. 布局元素 Layout Elements。
布局规范 Layout Specs

Layout spec 是 layout specification 的简称,没有物理存在。Layout spec 通过描述子布局元素相互关系来充当布局元素的容器。只能在layoutSpecThatFits:方法内创建、修改ASLayoutSpec,一旦将其传递回 ASDK,ASLayoutSpecisMutable属性将被设置为 NO。此后尝试修改ASLayoutSpec会引起断言。

Texture 提供了多个ASLayoutSpec的子类,如ASInsetLayoutSpecASStackLayoutSpecASBackgroundLayoutSpec等。

布局元素 Layout Elements

Layout spec 包含并排布 layout element。

ASDisplayNodeASLayoutSpec遵守ASLayoutElement协议,这意味着可以将 node 和 layout spec 组成 layout spec。

ASLayoutElement协议有几个可用于创建复杂布局的属性。此外,layout spec 具有自己的属性集,可用于调整布局元素的位置。

组合使用 layout spec 和 layout element 创建复杂 UI

通过下图可以看到如何将ASTextNode(黄色突出显示)、ASVideoNode(顶部图像)和ASStackLayoutSpec组合在一起以创建复杂的布局。

TextureLayoutSpecRelationship-1.png

ASVideoNode上的播放按钮使用ASCenterLayoutSpecASOverlayLayoutSpec布局。

TextureLayoutSpecRelationship-2.png

Node 大小

有些元素基于现有内容具有固有大小(intrinsic size)。例如,ASTextNode能够根据字符串计算自身大小。以下是具有 intrinsic size 的 node:

  • ASImageNode
  • ASTextNode
  • ASButtonNode

其他 node 要么没有 intrinsic size,要么在加载外部资源前没有固有大小。例如,ASNetworkImageNode在从 URL 下载完图片前没有固有大小。以下是不具有固有大小的 node:

  • ASVideoNode
  • ASVideoPlayerNode
  • ASNetworkImageNode
  • ASEditableTextNode

这些缺少初始固有大小的 node,必须使用ASRatioLayoutSpecASAbsoluteLayoutSpecstyle对象的size属性为其设置初始大小。

布局调试

ASDisplayASLayoutSpec上调用asciiArtStringasciiArtName,返回该类及其子类的ascii-art表现形式。如果为 node 或 layout spec 设置.debugName,该属性也会被显示到 ascii art 中。如下所示:

-----------------------ASStackLayoutSpec----------------------
|  -----ASStackLayoutSpec-----  -----ASStackLayoutSpec-----  |
|  |       ASImageNode       |  |       ASImageNode       |  |
|  |       ASImageNode       |  |       ASImageNode       |  |
|  ---------------------------  ---------------------------  |
--------------------------------------------------------------

你也可以输出ASLayoutElementstyle对象,这在调试大小属性时特别有用。

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

布局示例

下面的示例摘取自example project demo。

简单标题格式

TextureSimpleHeader.png

实现上述布局方案:

  • 垂直的ASStackLayoutSpec
  • 水平的ASStackLayoutSpec
  • 使用ASInsetLayoutSpec偏移整个 header。

下图显示了布局元素的组成(node 和 layout spec):

TextureSimpleHeaderDiagram.png
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    // When the username / location text is too long,
    // shrink the stack to fit onscreen rather than push content to the right, offscreen
    ASStackLayoutSpec *nameLocationStack = [ASStackLayoutSpec verticalStackLayoutSpec];
    nameLocationStack.style.flexShrink = 1.0;
    nameLocationStack.style.flexGrow = 1.0;
    
    // If fetching post location data from server,
    // check if it is available yet and include it if so
    if (_postLocationNode.attributedText) {
        nameLocationStack.children = @[_usernameNode, _postLocationNode];
    } else {
        nameLocationStack.children = @[_usernameNode];
    }
    
    // Horizontal stack
    ASStackLayoutSpec *headerStackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
                                                                                 spacing:40
                                                                          justifyContent:ASStackLayoutJustifyContentStart
                                                                              alignItems:ASStackLayoutAlignItemsCenter
                                                                                children:@[nameLocationStack, _postTimeNode]];
    
    // Inset the horizontal stack
    return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 10, 0, 10) child:headerStackSpec];
}

旋转设备可以查看间距变化。

带有 inset 文字叠加的图片

TextureInsetOverlay.png

实现上述布局方案:

  • 使用ASInsetLayoutSpecinset 文本。
  • 使用ASOverlayLayoutSpec将 inset 后的文本覆盖到图片上。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    CGFloat photoDimension = constrainedSize.max.width / 4.0;
    _photoNode.style.preferredSize = CGSizeMake(photoDimension, photoDimension);
    
    // INFINITY is used to make the inset unbounded
    UIEdgeInsets insets = UIEdgeInsetsMake(INFINITY, 12, 12, 12);
    ASInsetLayoutSpec *textInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_titleNode];
    
    return [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_photoNode overlay:textInsetSpec];
}

带有 outset 图标的图片

TextureOutsetOverlay.png

实现上述布局方案:

  • 使用ASCornerLayoutSpec将图标放到图片边角。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    _iconNode.style.preferredSize = CGSizeMake(40, 40);
    _photoNode.style.preferredSize = CGSizeMake(150, 150);
    
    return [ASCornerLayoutSpec cornerLayoutSpecWithChild:_photoNode
                                                  corner:_iconNode
                                                location:ASCornerLayoutLocationTopRight];
}

Inset 文本

TextureInsetCenter.png

实现上述布局方案:

  • 使用ASInsetLayoutSpec inset 文本。
  • 使用ASCenterLayoutSpec剧中文本。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    UIEdgeInsets insets = UIEdgeInsetsMake(0, 12, 4, 4);
    ASInsetLayoutSpec *inset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets
                                                                      child:_titleNode];
    
    return [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringY
                                                      sizingOptions:ASCenterLayoutSpecSizingOptionMinimumX
                                                              child:inset];
}

上下分割线

TextureSeparatorLine.png

布局方案如下:

  • 使用ASInsetLayoutSpec inset 文本。
  • 使用垂直方向的ASStackLayoutSpec将分割线堆叠在文本的顶部和底部。

下图显示了布局方案的组成:

TextureSeparatorLineDiagram.png
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
   // flexGrow、flexShrink 均为 BOOL 类型
    _topSeparator.style.flexGrow = 1.0;
    _bottomSeparator.style.flexGrow = 1.0;
    
    ASInsetLayoutSpec *insetContentSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(20, 20, 20, 20)
                                                                                 child:_textNode];
    
    return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
                                                   spacing:0
                                            justifyContent:ASStackLayoutJustifyContentCenter
                                                alignItems:ASStackLayoutAlignItemsStretch
                                                  children:@[_topSeparator, insetContentSpec, _bottomSeparator]];
}

布局规范 Layout Specs

TextureASLayouSpec.png

可以使用以下ASLayoutSpec的子类组建复杂布局。

  • ASWrapperLayoutSpec
  • ASStackLayoutSpec
  • ASInsetLayoutSpec
  • ASOverlayLayoutSpec
  • ASBackgroundLayoutSpec
  • ASRatioLayoutSpec
  • ASRelativeLayoutSpec
    • ASCenterLayoutSpec
  • ASAbsoluteLayoutSpec
  • ASCornerLayoutSpec

ASCenterLayoutSpec继承自ASRelativeLayoutSpec

可以通过继承ASLayoutSpec创建自定义布局规范。

ASWrapperLayoutSpec

ASWrapperLayoutSpec可以包装ASLayoutElement,并根据 layout element 大小计算子视图布局。

ASWrapperLayoutSpec适合于从layoutSpecThatFits:返回单个子节点,该子 node 可选设置大小。如果还需要设置位置信息,请使用ASAbsoluteLayoutSpec

// return a single subnode from layoutSpecThatFits:
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    return [ASWrapperLayoutSpec wrapperWithLayoutElement:_subnode];
}

// set a size (but not position)
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    _subnode.style.preferredSize = CGSizeMake(constrainedSize.max.width,
                                              constrainedSize.max.height / 2.0);
    return [ASWrapperLayoutSpec wrapperWithLayoutElement:subnode];
}

ASStackLayoutSpec (Flexbox container)

在 Texture 的所有 layoutSpecs 中,ASStackLayoutSpec是最有用、最强大的。ASStackLayoutSpec使用 flexbox 算法确定子视图的位置和大小。Flexbox 旨在不同尺寸的屏幕上提供一致的布局,在堆栈布局中(stack layout),可以在垂直或水平方向对齐项目,Stack layout 可以嵌套使用。这使得可以使用 stack layout 创建几乎所有布局。

Stack layout 可以垂直或水平堆叠 children。初始布局时,children 在主轴方向无限大布局,交叉轴方向采用布局约束。在主轴方向所有 children 大小相加,如果和小于布局规范的最小值,设置了flexGrow的 children 将被拉伸;如果和大于布局规范的最大值,设置了flexShrink的 children 将被压缩;如果拉伸、压缩后仍然不符合布局规范,children 的justifyContent决定 children 布局方式。

例如:堆叠方向为垂直方向,min-width = 100, max-width = 300, min-height = 200, max-height = 500。初始布局时,所有 children 按照min-width = 100, max-width = 300, min-height = 0, max-height = INFINITY 布局。如果 children 高度和小于200,设置了flexGrow属性的 children 将会拉伸,如果 children 高度和大于500,设置了flexShrink属性的 children 将被压缩。每个 child 压缩高度为 总高度 - 500 / 可压缩 child 数量。如果压缩后高度依然大于500,justifyContent属性决定 children 布局方式。

ASStackLayoutSpec除了ASLayoutElement属性外,还具有以下属性:

  • direction:设置 children 堆叠方向。如果设置了verticalAlignmenthorizontalAlignment,将再次解析。从而导致justifyContentalignItems相应地更新。
  • spacing:child 间距。
  • horizontalAlignment:指定 children 水平对齐方式。根据堆栈方向,设置对齐方式将导致justifyContentalignItems被更新。后续堆栈方向改变时,此属性依然有效。
  • verticalAlignment:指定 children 垂直对齐方式。根据堆栈方向,设置对齐方式将导致justifyContentalignItems被更新。后续堆栈方向改变时,此属性依然有效。
  • justifyContent:指定沿主轴的对齐方式。justifyContent属性决定了浏览器如何沿 flex 容器主轴和内连轴(inline axis)在内容项之间及其周围分配空间。采用该属性前会先根据flexGrowflexShrink进行拉伸压缩,拉伸、压缩后依然超出、小于可用空间,会根据该属性进行布局。例如,justifyContent设置为ASStackLayoutJustifyContentStart时,如果最小占用空间超出规范大小,则超出部分在右下角;如果小于规范大小,则居左上角显示。点击justify-content查看更为详细介绍。
  • alignItems:指定 children 沿交叉轴的排列方式。
  • flexWrap:children 排布在一列还是多列。默认排布为一列。
  • alignContent:如果排布为多列,沿交叉轴排布方式。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    ASStackLayoutSpec *mainStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
                                                                           spacing:6.0
                                                                    justifyContent:ASStackLayoutJustifyContentStart
                                                                        alignItems:ASStackLayoutAlignItemsCenter
                                                                          children:@[_iconNode, _countNode]];
    
    // Set some constrained size to the stack
    mainStack.style.minWidth = ASDimensionMakeWithPoints(60.0);
    mainStack.style.maxHeight = ASDimensionMakeWithPoints(40.0);
    
    return mainStack;
}

可以查看Flexbox playground了解每个属性的具体布局方式。

ASInsetLayoutSpec

在布局传递期间,ASInsetLayoutSpec减去 inset 之后,将其 constrainedSize.max 传递给 child。child 确定其最终尺寸后,添加上 inset 即为最终尺寸。由于ASInsetLayoutSpec的布局是根据 child 尺寸确定的,因此 child 必须具有固有大小,或显式设置其大小。

TextureASInsetLayoutSpec-diagram.png

如果UIEdgeInsets值设置为INFINITY,inset spec 将使用 child 的固有大小。

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    ...
    UIEdgeInsets *insets = UIEdgeInsetsMake(10, 10, 10, 10);
    ASInsetLayoutSpec *headerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:textNode];
    ...
}

ASOverlayLayoutSpec

ASOverlayLayoutSpec布局 child(蓝色),并在其顶部拉伸另一个组件作为覆盖层(红色)。

TextureASOverlayLayouSpec-diagram.png

Overlay 的大小根据 child 大小计算而来。在上图中,child 是蓝色部分,child 的大小作为constrainedSize传递给 overlay 布局元素。因此,child 必须具有固有大小,或显式设置大小。

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    _blueNode.style.preferredSize = CGSizeMake(150, 150);
    ASInsetLayoutSpec *redInsetLayoutSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(30, -30, -30, 30) child:_redNode];
    return [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_blueNode overlay:redInsetLayoutSpec];
}

ASBackgroundLayoutSpec

ASBackgroundLayoutSpec布局一个组件 child(蓝色),并拉伸另一个组件作为背景(红色)。

TextureASBackgroundLayoutSpec-diagram.png

背景组件的大小根据 child(蓝色)大小决定。布局时 child 大小会作为constrainedSize传递给背景组件(红色)。因此,child 必须具有固有大小,或显式设置大小。

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    _blueNode.style.preferredSize = CGSizeMake(150, 150);
    ASInsetLayoutSpec *redInsetLayoutSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(-30, 30, 30, -30) child:_redNode];
    return [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:_blueNode background:redInsetLayoutSpec];
}

ASBackgroundLayoutSpec布局时,子节点添加的顺序会影响布局结果。必须先添加背景组件,后添加 child组件。使用自动子节点管理(automatic subnode management,简称ASM)并不能确保其添加顺序。

ASCenterLayoutSpec

ASCenterLayoutSpec将 child 布局于最大constrainedSize中。

TextureASCenterLayoutSpec-diagram.png

如果没有限制ASCenterLayoutSpec的宽度、高度,则其会缩放到 child 大小。

ASCenterLayoutSpec包含以下属性:

  • centeringOptions:确定 child 如何在 center spec 内居中,选项包括:none、X、Y、XY。
  • sizingOptions:确定 center spec 占用空间。选项包括:Default、MinimumX、MinimumY、MinimumXY。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    ASStaticSizeDisplayNode *subnode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(70, 100));
    return [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY
                                                      sizingOptions:ASCenterLayoutSpecSizingOptionDefault
                                                              child:subnode]
}

ASRatioLayoutSpec

ASRatioLayoutSpec用于固定组件纵横比,其可以缩放。ASRatioLayoutSpec必须提供宽度或高度,用以限制组件大小。

TextureASRatioLayoutSpec-diagram.png

由于ASNetworkImageNodeASVideoNode在服务器返回内容前没有固有大小,经常使用ASRatioLayoutSpecASNetworkimageNodeASVideoNode提供固有大小。

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    // Half Ratio
    ASStaticSizeDisplayNode *subnode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(100, 100));
    return [ASRatioLayoutSpec ratioLayoutSpecWithRatio:0.5
                                                 child:subnode];
}

ASRelativeLayoutSpec

ASRelativeLayoutSpec根据水平、垂直位置标志符在布局范围内进行布局。类似于九宫格图片,child 可以位于四角、边线中间或中心。

ASRelativeLayoutSpec类非常强大,可以查看ASRelativeLayoutSpeccalculateLayoutThatFits:方法了解具体实现细节:

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    ...
    ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
    ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(70, 100));
    
    ASRelativeLayoutSpec *relativeSpec = [ASRelativeLayoutSpec relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
                                                                                               verticalPosition:ASRelativeLayoutSpecPositionStart
                                                                                                   sizingOption:ASRelativeLayoutSpecSizingOptionDefault
                                                                                                          child:foregroundNode]
    
    ASBackgroundLayoutSpec *backgroundSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:relativeSpec background:backgroundNode];
    ...
}

ASAbsoluteLayoutSpec

ASAbsoluteLayoutSpec通过设置layoutPosition属性指定 children 具体位置。与其他布局方式相比,ASAbsoluteLayoutSpec不灵活且难以维护。

ASAbsoluteLayoutSpec只有一个属性:

  • sizingASAbsoluteLayoutSpecSizing指定 spec 占用空间,ASAbsoluteLayoutSpecSizingDefault尽可能占用更多空间;ASAbsoluteLayoutSpecSizingSizeToFit计算出的大小是所有 frame 的和。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    CGSize maxConstrainedSize = constrainedSize.max;
    
    // Layout all nodes absolute in a static layout spec
    guitarVideoNode.style.layoutPosition = CGPointMake(0, 0);
    guitarVideoNode.style.preferredSize = CGSizeMake(maxConstrainedSize.width, maxConstrainedSize.height / 3.0);
    
    nicCageVideoNode.style.layoutPosition = CGPointMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0);
    nicCageVideoNode.style.preferredSize = CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0);
    
    simonVideoNode.style.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height - (maxConstrainedSize.height / 3.0));
    simonVideoNode.style.preferredSize = CGSizeMake(maxConstrainedSize.width/2, maxConstrainedSize.height / 3.0);
    
    hlsVideoNode.style.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height / 3.0);
    hlsVideoNode.style.preferredSize = CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0);
    
    return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[guitarVideoNode, nicCageVideoNode, simonVideoNode, hlsVideoNode]];
}

ASCornerLayoutSpec

ASCornerLayoutSpec可以便捷的在四个角布局元素。将元素放置在角落的简单方法是使用声明性代码表达式,而非手动计算坐标。ASCornerLayoutSpec可以很方便的实现这项任务。

TextureASCornerLayoutSpec.png

ASCornerLayoutSpec可以计算自身大小。ASCornerLayoutSpec的用途之一是为头像添加角标,这时无需担心超出头像部分的 frame 会影响整个布局大小。默认情况下,角标元素大小不会计算进布局大小;如果需要计算进去,手动设置wrapsCorner属性为YES

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    ...
    // Layout the center of badge to the top right corner of avatar.
    ASCornerLayoutSpec *cornerSpec = [ASCornerLayoutSpec cornerLayoutSpecWithChild:self.avatarNode corner:self.badgeNode location:ASCornerLayoutLocationTopRight];
    // Slightly shift center of badge inside of avatar.
    cornerSpec.offset = CGPointMake(-3, 3);
    ...
}

ASLayoutSpec

ASLayoutSpec是所有布局方案的父类。其主要功能是管理所有 children,但也可用于创建子类。只有极少高级用户需要创建ASLayoutSpec的子类,尽可能利用上面提供的类组合创建复杂布局。

ASLayoutSpec的另一用途是为ASStackLayoutSpec提供空间,此时需要设置flexGrowflexShrink

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    ...
    // ASLayoutSpec as spacer
    ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
    spacer.style.flexGrow = 1.0;
    
    stack.children = @[imageNode, spacer, textNode];
    ...
}

布局元素的属性

TextureLayoutElementProperties.png
  • ASStackLayoutElement Properties 可用于任何 node 或 layoutSpec,但只对 stack spec 的 child 有效。
  • ASAbsoluteLayoutElement Properties 只对 absolute spec 的 child 有效。
  • ASLayoutElement properties 对所有 node 和 layout spec 有效。

ASStackLayoutElement Properties

属性 描述
CGFloat
.style.spacingBefore
在 stack 方向、该对象前面的额外空间。
CGFloat
.style.spacingAfter
在 stack 方向、该对象后面的额外空间
CGFloat
.style.flexGrow
如果 stack 的和小于允许的最小值,该对象是否拉伸。
CGFloat
.style.flexShrink
如果 stack 的和大于允许的最大值,该对象是否压缩。
ASDimension
.style.flexBasis
在应用flexGrowflexShrink属性并分配剩余空间前,在 stack 方向上的初始大小。
ASStackLayoutAlignSelf
.style.alignSelf
对象沿交叉轴方向对齐方式,重写alignItems属性。选项包括:ASStackLayoutAlignSelfAuto、ASStackLayoutAlignSelfStart、ASStackLayoutAlignEnd、ASStackLayoutAlignSelfCenter、ASStackLayoutAlignSelfStretch。
CGFloat
.style.ascender
用于基线对齐方式。从对象顶部至基线的距离。
CGFloat
.style.descender
用于基线对齐方式。从对象基线至顶部的距离。

ASAbsoluteLayoutElement Properties

属性 描述
CGPoint
.style.layoutPosition
对象在ASABsoluteLayoutSpec规范中的CGPoint位置。

ASLayoutElement properties

属性 描述
ASDimension
.style.width
width属性指定ASLayoutElement内容区域宽度,minWidthmaxWidth属性会重写width。即当width值小于minWidth时,以minWidth为准。默认为ASDimensionAuto
ASDimension
.style.height
height属性指定ASLayoutElement内容区域高度,minHeightmaxHeight属性会重写width。默认为ASDimensionAuto
ASDimension
.style.minWidth
minWidth属性用于设定给定元素的最小宽度。minWidth的值会重写maxWidthwidth。默认为ASDimensionAuto
ASDimension
.style.maxWidth
maxWidth属性用于设定给定元素的最大宽度。maxWidth属性会阻止width的值大于maxWidthmaxWidth属性重写widthminWidth属性重写maxWidth。默认为ASDimensionAuto
ASDimension
.style.minHeight
minHeight属性用于设定给定元素的最小高度。minHeight属性会阻止height的值小于minHeightminHeight会重写maxHeightheight。默认为ASDimensionAuto
ASDimension
.style.maxHeight
maxHeight属性用于设定给定元素的最大高度。maxHeight属性会阻止height的值大于maxHeightmaxHeight会重写heightminHeight会重写maxHeight。默认为ASDimensionAuto
CGSize
.style.preferredSize
为布局元素设置推荐大小。如果设置了可选值minSizemaxSize,且preferredSize超过了其限制,则采用minSizemaxSize的值。如果未设置preferredSize,则默认采用calculateSizeThatFits:计算出的固有大小。
该属性可选设置。如果 node 没有固有大小或需要设置为其他大小,则需要设置preferredSize属性。例如,为ASDisplayNode设置不同于图片大小的值。
当 size 为相对大小时,调用heightwidth的取值方法会触发断言。
CGSize
.style.minSize
为布局元素设置最小尺寸,可选属性。如果设置了该属性,则强制满足该属性。如果父视图最小尺寸小于子视图最小尺寸,则强制满足子视图最小尺寸,父视图尺寸会超出其自身限制。
CGSize
.style.maxSize
为布局元素设置最大尺寸,可选属性。如果设置了该属性,则强制满足该属性。如果子视图最大尺寸小于父视图限制,则采用子视图尺寸限制。
ASLayoutSize
.style.preferredLayoutSize
为布局元素提供相对大小,可选属性。ASLayoutSize使用百分比而非 point 指定大小。例如,宽度为父视图 50%。如果提供了可选的minLayoutSizemaxLayoutSize,且preferredLayoutSize超过了minLayoutSizemaxLayoutSize的限制,则采用minLayoutSizemaxLayoutSize属性的值。如果未提供,则采用calculateSizeThatFits:计算的固有大小。
ASLayoutSize
.style.minLayoutSize
为布局元素提供相对大小的最小值,可选属性。如果提供了该属性,则强制满足该限制。如果父级布局最小相对尺寸小于子级最小相对尺寸,则强制满足子级最小尺寸。
ASLayoutSize
.style.maxLayoutSize
为布局元素提供相对大小的最大值,可选属性。如果提供了该属性,则强制满足该限制。如果父级布局最大相对尺寸小于子级最大相对尺寸,则强制满足子级最大尺寸。

布局大小单位

下面介绍布局大小的相关单位。

Textureapi-sizing.png

Values(CGFloat,ASDimension)

ASDimension本质上是一个普通的CGFloat,其支持 point、百分比或自动值。

同一 API 支持固定值和相对值。

// dimension returned is relative (%)
ASDimensionMake(@"50%");
ASDimensionMakeWithFraction(0.5);

// dimension returned in points
ASDimensionMake(@"70pt");
ASDimensionMake(70);
ASDimensionMakeWithPoints(70);

ASDimension用于设置ASStackLayoutSpec child 的flexBasis属性。flexBasis指定 stack 轴向初始大小。

在下图中,水平 stack 左侧占比 50%,右侧占比 60%。

Textureflexbasis.png

通过设置横向 stack children 的flexBasis属性可以实现上述需求。

self.leftStack.style.flexBasis = ASDimensionMake(@"40%");
self.rightStack.style.flexBasis = ASDimensionMake(@"60%");

[horizontalStack setChildren:@[self.leftStack, self.rightStack]];

Sizes(CGSize, ASLayoutSize)

ASLayoutSizeCGSize相似,但其宽度和高度可以使用 points 和百分比表示。高度和宽度可以是不同表示类型。

ASLayoutSizeMake(ASDimension width, ASDimension height);

ASLayoutSize用于设置布局元素的preferredLayoutSizeminLayoutSizemaxLayoutSize属性。其允许同一 API 同时使用固定大小和相对大小。

// Dimension type "Auto" indicates that the layout element may 
// be resolved in whatever way makes most sense given the circumstances
ASDimension width = ASDimensionMake(ASDimensionUnitAuto, 0);  
ASDimension height = ASDimensionMake(@"50%");

layoutElement.style.preferredLayoutSize = ASLayoutSizeMake(width, height);

如果不需要相对尺寸,可以使用preferredSizeminSizemaxSize属性。这些属性使用常规CGSize值。

layoutElement.style.preferredSize = CGSizeMake(30, 160);

通常情况下,不需同时设置高度和宽地。这种情况下可以使用ASDimension设置所需值。

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%");

Size Range(ASSizeRange)

UIKit没有提供最小、最大CGSize结构。因此,创建了ASSizeRange以支持最小和最大CGSize结构。

ASSizeRange通常用在布局API内部,但layoutSpecThatFits:的参数是ASSizeRange

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize;

传递给layoutSpecThatFits:的 constrainedSize 参数包含了 node 需满足的最小、最大尺寸,用以规范 node 尺寸。

Texture 也可以使用动画,具体可以查看Layout Transition API

上一篇:Texture 核心概念

下一篇:Texture 便捷方法

欢迎更多指正:https://github.com/pro648/tips

本文地址:https://github.com/pro648/tips/blob/master/sources/Texture%20%E5%B8%83%E5%B1%80%20Layout.md

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容