Texture 目标&优点
Texture 的 Layout API是 UIKit AutoLayout 的高效替代品
- Fast:自动布局比 AutoLayout 快的多
- Asynchronous & Concurrent: Layout 在后台计算,所以用户交互不会被终端
- Declarative:布局用不可变的数据结构声明。这使布局代码更易于开发,文档编制,代码审查,测试,调试,配置文件和维护。
- Cacheable:布局结果是不可变的数据结构,因此可以在后台对其进行预先计算并进行缓存以提高用户的感知性能。
- Extensible:易于在类之间共享代码。
受 CSS Flexbox 模型启发flexbox model
基本概念
Texture 布局系统的两个核心概念:
- Layout Specs
- Layout Elements
Layout Specs
class ASLayoutSpec
布局规范(layout specification)通过了解这些子布局元素之间的相互关系布局元素,是布局元素的容器。
Layout Elements
protocol <ASLayoutElement>
Layout Specs 包含并排列 Layout Elements
所有 ASDisplayNodes
& ASLayoutSpecs
都遵循
1 | -----------------------ASStackLayoutSpec---------------------- |
Layout Specs 子类
- ASWrapperLayoutSpec
- ASStackLayoutSpec
- ASInsetLayoutSpec
- ASOverlayLayoutSpec
- ASBackgroundLayoutSpec
- ASCenterLayoutSpec
- ASRatioLayoutSpec
- ASRelativeLayoutSpec
- ASAbsoluteLayoutSpec
- ASCornerLayoutSpec
ASWrapperLayoutSpec
它可以包裹一个 ASLayoutElement
,通过这个 element 的siez set 计算这个 element 的layout。
用于:ASWrapperLayoutSpec
对于在 -layoutSpecThatFits:
中包裹单个 node,返回spec.
1 | // return a single subnode from layoutSpecThatFits: |
ASInsetLayoutSpec
给 element 添加 inset margin。element 必须有 instrinsic size or 显示设置的size
1 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { |
ASOverlayLayoutSpec
重叠布局规范尺寸是根据 element 的尺寸计算得出的。在下图中,element 是蓝色层
重叠布局规范尺寸是根据包裹 element 的尺寸计算出来的。在下图中,子元素是蓝色图层。蓝色 element 会把他的 size 作为 constrainedSize
传给重叠布局元素(红色图层)。所以 element (蓝色layer)必须要有自己的 intrinsic size 或者 已经设置 size了。
1 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec{ |
ASBackgroundLayoutSpec
ASBackgroundLayoutSpec 布局一个组件(蓝色),并拉伸其后的另一个组件作为背景(红色)。
background 规范 size根据 child 的尺寸计算。下图中,蓝色是child。将 blue child 的size 当做 constrainedSize
传给 background layout element (red layer). so child(blue layer) 一定要有自己的intrinsic size 或者 已经设置 size了。
1 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { |
ASCenterLayoutSpec
ASCenterLayoutSpec 在最大 constrainedSize
将放在 child 中心位置。
如果center spec’s width 和 height 没有限制,那么 ASCenterLayoutSpec 缩到 child 的size大小。
ASCenterLayoutSpec
两个属性:
- centeringOptions. 确定 child 如何在中心规格内居中。选项包括:None, X, Y, XY.
- sizingOptions. 确定中心规格将占用多少空间。选项包括:Default, minimum X, minimum Y, minimum XY.
1 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { |
ASRatioLayoutSpec
ASRatioLayoutSpec 使用固定宽高比来布局组件。ASRatioLayoutSpec 他的 constrainedSize
必须要配置 width or height。这样才能够使用 scale
ASRatioLayoutSpec 经常用于那些没有 intrinsic size 的node(ASNetworkImageNode or ASVideoNode)。
1 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { |
ASRelativeLayoutSpec
并根据垂直和水平位置说明符对组件进行布局.
与“ 9部分”图像区域相似,可以将孩子放置在4个角的任意一个或4个边缘中的任意一个的中间以及中心。
1 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { |
ASAbsoluteLayoutSpec
通过设置其子节点的layoutPosition属性来指定其子节点的确切位置(x / y坐标)。
绝对布局比其他类型的布局更不灵活且难以维护。
唯一属性sizing: 确定 ASAbsoluteLayoutSpec 占用多少空间。选项包括:Default 和 Size to Fit。请注意,Size to Fit 选项将复制旧的ASStaticLayoutSpec的行为
1 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { |
ASCornerLayoutSpec
ASCornerLayoutSpec 提供一个简单方法,把 element 放在角落。
1 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec{ |
ASLayoutSpec
- 所有 layout spec 都继承自它
- 主要任务:处理 children 布局管理,用户可以 子类化它实现自己自定义 layout spec。
- 作为间隔区:充当
ASStackLayoutSpec
的spacer,当使用.flexGrow
and/or.flexShrink
时候
1 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { |
ASStackLayoutSpec(Flexbox Container)
相关属性对照flexbox-model
有 7 个属性:
- direction. 确定 stack 方向。如果已经设置了 horizontalAlignment and verticalAlignment, 他们会被重新解析一遍。导致 justifyContent and alignItems也会相应的变化。
- spacing. 每个 child element 之间的距离.
- horizontalAlignment. 指定 children 是如何水平对齐的。根据stack direction方向。设置对齐方式 会导致 justifyContent or alignItems 更新。
- verticalAlignment. 指定 children 是如何垂直对齐的。根据stack direction方向。设置对齐方式 会导致 justifyContent or alignItems 更新。
- justifyContent. 沿主轴的对齐方式.
- alignItems. 沿交叉轴的方向布局.
- flexWrap. element 堆叠成单行还是多行。默认为单行。
- alignContent. 元素组成有多行,cross-axis方向空间充足,内容行的对齐方式。
1 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { |
Layout Element 属性
- ASStackLayoutElement Properties:只会在 StackLayoutSpec 中的元素(node or layout spec)生效
- ASAbsoluteLayoutElement Properties:只会在AbsoluateLayoutSpec中的元素( subnode 或 layoutSpec)生效;
- ASLayoutElement Properties:适用于所有 Node 和 layoutSpec;
ASStackLayoutElement(flex-item)属性
相关属性对照flexbox-model
属性 | 类型 | 描述 |
---|---|---|
.style.spacingBefore | CGFloat | direction 上与前一个 node 的间隔 |
.style.spacingAfter | CGFloat | direction 上与后一个 node 的间隔 |
.style.flexGrow | Bool | 子节点尺寸总和小于 minimum即存在剩余空间时,是否放大 |
.style.flexShrink | Bool | 子节点总和大于 maximum,即空间不足时,是否缩小 |
.style.flexBasis | ASDimension | 在使用flexGrow 或 flexShrink 属性之前,并且剩余空间被均分之前,指定item的初始size |
.style.alignSelf | ASStackLayoutAlignSelf | item 在cross-axis方向布局方式,此属性会覆盖 layoutSpec(flex-container) 属性alignItems,可选值有:Auto、Start、End、Center、Stretch |
.style.ascender | CGFloat | 用于基线对齐,描述对象从顶部到其基线的距离 |
.style.descender | CGFloat | 用于基线对齐,描述对象从基线到其底部的距离 |
ASAbsoluteLayoutElement 属性
属性 | 类型 | 描述 |
---|---|---|
.style.layoutPosition | CGPoint | 该对象在 ASAbsoluteLayoutSpec 中的位置 |
ASLayoutElement 属性
属性 | 类型 | 描述 |
---|---|---|
.style.width | ASDimension | 指定 ASLayoutElement 内容区域的宽度。minWidth 和 maxWidth 属性会覆盖 width,默认值为 ASDimensionAuto |
.style.height | ASDimension | 指定ASLayoutElement 内容区域的高度。minHeight 和 maxHeight 属性会覆盖 height,默认值为 ASDimensionAuto |
.style.minWidth | ASDimension | minWidth 属性用于设置一个特定布局元素的最小宽度.它可以防止 width 属性值小于 minWidth 指定的值,minWidth 的值会覆盖 maxWidth 和 width. 默认值为 ASDimensionAuto |
.style.maxWidth | ASDimension | maxWidth 属性用于设置一个特定布局元素的最大宽度.它可以防止 width 属性值大于 maxWidth 指定的值. maxWidth 的值会覆盖 width,minWidth 会覆盖 maxWidth. 默认值为 ASDimensionAuto |
.style.minHeight | ASDimension | minHeight 属性用于设置一个特定布局元素的最小高度.它可以防止 height 属性值小于 minHeight 指定的值.minHeight 的值会覆盖 maxHeight 和 height. 默认值为 ASDimensionAuto |
.style.maxHeight | ASDimension | maxHeight 属性用于设置一个特定布局元素的最大高度,它可以防止 height 属性值大于 maxHeight 指定的值.maxHeight 的值会覆盖 height,minHeight 会覆盖 maxHeight.默认值为 ASDimensionAuto |
.style.preferredSize | CGSize | 提供布局元素的建议 size.如果提供了 minSize 或 maxSize,并且 preferredSize 超过了这些值,则强制使用 minSize 或 maxSize.如果未提供 preferredSize,则布局元素的 size 默认为 calculateSizeThatFits: 方法提供的固有大小.此方法是可选的,但是对于没有固有大小或需要用与固有大小不同的的 size 进行布局的节点,则必须指定 preferredSize 或 preferredLayoutSize 中的一个,比如没这个属性可以在 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 将会强制使用 |
.style.preferredLayoutSize | ASLayoutSize | 为布局元素提供建议的相对 size.ASLayoutSize 使用百分比而不是点来指定布局.例如,子布局元素的宽度应该是父宽度的 50%.如果提供了可选的 minLayoutSize 或 maxLayoutSize,并且 preferredLayoutSize 超过了这些值,则将使用 minLayoutSize 或 maxLayoutSize |
.style.minLayoutSize | ASLayoutSize | 可选属性,为布局元素提供最小的相对尺寸, 如果提供,minLayoutSize 将会强制使用.如果父级布局元素的 minLayoutSize 小于其子级的 minLayoutSize,则会强制使用子级的 minLayoutSize,并且其大小将扩展到布局规则之外 |
.style.maxLayoutSize | ASLayoutSize | 可选属性,为布局元素提供最大的相对尺寸.如果提供,maxLayoutSize 将会强制使用.如果父级布局元素的 maxLayoutSize 小于其子级的 maxLayoutSize,那么将强制使用子级的 maxLayoutSize,并且其大小将扩展到布局规则之外 |
Layout API Sizing
在Layout API中,理解复合尺寸类型的最简单方法是查看所有的 unit 之间的关系。
Values (CGFloat, ASDimension)
ASDimension本质上是一个普通的CGFloat,支持表示点值,相对百分比值或自动值。
unit 允许相同的API接受固定值和相对值。
1 | // dimension returned is relative (%) |
使用 ASDimension 的例子
ASDimension 用于设置 ASStackLayoutSpec
中element 的 flexBasis
. flexBasis
属性在 stack layout 中指定对象的初始大小,
在下面的视图中,我们希望左侧堆栈占据水平宽度的40%,右侧堆栈占据宽度的60%。
为此,我们在水平堆栈的两个子级上设置.flexBasis属性:
1 | self.leftStack.style.flexBasis = ASDimensionMake("40%") |
Sizes (CGSize, ASLayoutSize)
ASLayoutSize与CGSize相似,但是其宽度和高度值可以表示点或百分比值。宽度和高度的类型是独立的;一个可以是点或百分比值。
1 | ASLayoutSizeMake(ASDimension width, ASDimension height); |
ASLayoutSize用于设置布局元素的 .preferredLayoutSize
, .minLayoutSize
和 .maxLayoutSize
属性。它允许相同的API接受固定大小以及相对大小。
1 | // Dimension type "Auto" indicates that the layout element may |
如果不需要相对值,则可以设置布局元素的.preferredSize,.minSize和.maxSize属性。这些属性采用常规CGSize值。
1 | layoutElement.style.preferredSize = CGSize(width: 30, height: 60) |
大多数时候,都不想同时限制宽度和高度。在这种情况下,可以使用ASDimension值分别设置布局元素的尺寸属性。
1 | layoutElement.style.width = ASDimensionMake("50%") |
Size Range (ASSizeRange)
UIKit没有提供组合最小和最大CGSize的 struct。因此,创建了ASSizeRange以支持最小和最大CGSize对。
ASSizeRange通常用于布局API的内部。但是,作为输入传递给layoutSpecThatFits:的constrainedSize值是ASSizeRange。
1 | func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec |
传递给ASDisplayNode子类的layoutSpecThatFits:方法的constrainedSize是节点应适合的最小和最大大小。constrainedSize中包含的最小CGSize和最大CGSize可用于调整节点的布局元素的大小。