Texture快速开始(C)
1.1: Motivation & Benefits
对于复杂的视图层次结构来说,UIKit的布局方式将变得比较低效。Layout API
是作为UIKit Auto Layout
的高性能替代品创建的。Texture的布局API比使用UIKit的自动布局有以下优秀的特点:
- 1: 快速:与手动布局代码一样快,比自动布局快得多
- 2: 异步和并发:布局可以在后台线程上计算,因此用户交互流畅和及时响应。
- 3: 声明性:布局是用不可变的数据结构声明的。这使得布局代码更容易开发,文档,代码审查,测试,调试,配置文件和维护。
- 4: 可缓存:布局结果是不可变的数据结构,因此它们可以在后台预先计算并缓存以提高用户感知的性能。
- 5: 可扩展:易于在类之间共享代码。
1.2: 受CSS Flexbox的启发
熟悉Flexbox的人会注意到这两个系统有许多相似之处。但是,Texture的Layout API不会重新实现所有的CSS。
1.3: 基本概念
Texture
的布局系统围绕着两个基本概念:
- 布局规格
- 布局元素
1.4: 布局规范
布局规范(简称“布局规范”)没有实体存在。相反,通过了解这些子布局元素如何相互关联,布局规范充当其他布局元素的容器。
Texture提供了ASLayoutSpec
的几个子类,从一个插入单个子元素的简单布局规范到更复杂的布局规范,这些布局规范将多个子元素排列在不同的堆栈配置中。
1.5: 布局元素
布局规范包含并排列布局元素。
所有ASDisplayNodes
和ASLayoutSpecs
都符合
协议。这意味着您可以从节点和其他布局规格构建布局规格。cool!
ASLayoutElement
协议有几个属性可以用来创建非常复杂的布局。此外,布局规范还有其自己的一组属性,可用于调整布局元素的排列。
1.6: 结合布局规范和布局元素来制作复杂的用户界面
在这里,您可以看到如何组合ASTextNodes
(以黄色突出显示),ASVideoNode
(顶部图像)和ASStackLayoutSpec
(“堆栈布局规格”)来创建复杂布局。
使用 ASCenterLayoutSpec
(“中央布局规范”)和ASOverlayLayoutSpec
(“覆盖布局规范”)放置ASVideoNode
顶部的播放按钮(顶部图像)。
1.7: 有些节点需要尺寸集
一些元素基于必须给定固定大小。例如,ASTextNode
可以根据其属性字符串计算其大小。有些node需要固定尺寸的包括
- ASImageNode
- ASTextNode
- ASButtonNode
所有其他节点在加载外部资源之前,不具有固有大小或缺少固有大小。例如,ASNetworkImageNode在
从URL下载图像之前不知道其大小。 这些元素包括
- ASVideoNode
- ASVideoPlayerNode
- ASNetworkImageNode
- ASEditableTextNode
缺少初始固有尺寸的这些节点必须使用ASRatioLayoutSpec
,ASAbsoluteLayoutSpec
或样式对象上的尺寸属性为它们设置初始尺寸。
1.8: 布局调试
在任何ASDisplayNode
或ASLayoutSpec
上调用-asciiArtString
都会返回对象及其子对象的ascii-art
表示形式。 或者,如果您在任何节点或布局规范上设置.debugName
,那么它也将包含在ascii art
中。 下面是一个例子。
-----------------------ASStackLayoutSpec----------------------
| -----ASStackLayoutSpec----- -----ASStackLayoutSpec----- |
| | ASImageNode | | ASImageNode | |
| | ASImageNode | | ASImageNode | |
| --------------------------- --------------------------- |
--------------------------------------------------------------
您还可以在任何ASLayoutElement
(节点或布局规范)上打印样式对象。这在调试大小属性时特别有用。
(lldb) po _photoImageNode.style
Layout Size = min {414pt, 414pt} <= preferred {20%, 50%} <= max {414pt, 414pt}
二: Layout Examples
查看布局规范示例项目以使用下面的代码。
带左右对齐文本的简单标题
要创建这个布局,我们将使用:
- 垂直ASStackLayoutSpec
- 和水平ASStackLayoutSpec
- ASInsetLayoutSpec插入整个标题
下图显示了布局元素的组成(节点+布局规格)。
- (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];
}
2.1:带有嵌入文字叠加层的照片
要创建这个布局,我们将使用:
-
ASInsetLayoutSpec
插入文本 -
ASOverlayLayoutSpec
将插图文字说明覆盖在照片上方
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
_photoNode.style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT*2, USER_IMAGE_HEIGHT*2);
// INIFINITY 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];
}
[ASOverlayLayoutSpec overlayLayoutSpecWithChild:_photoNode overlay:textInsetSpec]
中的_photoNode
的尺寸为平铺视图的大小 因此此时_photoNode.style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT*2, USER_IMAGE_HEIGHT*2)无用
2.2:带有初始图标叠加的照片
要创建此布局,我们将使用:
ASAbsoluteLayoutSpec
放置照片和图标,这些照片和图标已经使用其ASLayoutable
属性单独调整大小和放置
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
_iconNode.style.preferredSize = CGSizeMake(40, 40);
_iconNode.style.layoutPosition = CGPointMake(150, 0);
_photoNode.style.preferredSize = CGSizeMake(150, 150);
_photoNode.style.layoutPosition = CGPointMake(40 / 2.0, 40 / 2.0);
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithSizing:ASAbsoluteLayoutSpecSizingSizeToFit
children:@[_photoNode, _iconNode]];
}
2.3: 简单的插入文本单元格
要重新创建上面Pinterest的搜索视图中使用的单个单元格的布局,我们将使用:
-
ASInsetLayoutSpec
插入文本 -
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];
}
2.4: 顶部和底部分隔线
要创建上面的布局,我们将使用a:
- 一个
ASInsetLayoutSpec
来插入文本 - 垂直
ASStackLayoutSpec
将文本顶部和底部的两个分隔线叠加起来
下图显示了可布局对象的组成(布局规格+节点)。
以下代码也可以在ASLayoutSpecPlayground示例项目中找到。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
_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
以下ASLayoutSpec
子类可用于组成简单或非常复杂的布局。
ASWrapperLayoutSpec
ASStackLayoutSpec
ASInsetLayoutSpec
ASOverlayLayoutSpec
ASBackgroundLayoutSpec
ASCenterLayoutSpec
ASRelativeLayoutSpec
ASAbsoluteLayoutSpec
-
ASCornerLayoutSpec
- 您也可以将ASLayoutSpec子类化,以制定自己的自定义布局规格。
ASWrapperLayoutSpec
ASWrapperLayoutSpec
是一个简单的ASLayoutSpec
子类,可以包装ASLayoutElement
并根据在layout
元素上设置的大小来计算子级的布局。
ASWrapperLayoutSpec
是从-layoutSpecThatFits
轻松返回单个子节点的理想选择。 (可选)可以在此子节点上设置大小信息。然而,如果您需要设置大小以外的位置,请改用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
旨在在不同的屏幕尺寸上提供一致的布局. 在堆栈布局中,您可以在垂直或水平堆栈中对齐项目。堆栈布局可以是另一个堆栈布局的子级,这使得可以使用堆栈布局规范创建几乎任何布局。
ASStackLayoutSpec
除了其
属性外,还具有7个属性:
- direction: 指定子级堆叠的方向。如果设置了
horizontalAlignment
和verticalAlignment
,它们将再次解析,导致justifyContent
和alignItems
相应地更新 - spacing: 每个子node之间的空间量
- horizontalAlignment(主轴方向): 指定子项如何水平对齐。根据堆栈方向,设置对齐方式将导致
justifyContent
或alignItems
被更新。以后的方向更改后,路线将保持有效。对这些属性而言是首选。 - verticalAlignment(纵轴方向): 指定子项如何垂直对齐。根据堆栈方向,设置对齐方式将导致
justifyContent
或alignItems
被更新。以后的方向更改后,路线将保持有效。因此,对这些属性而言是首选。 - justifyContent: 它定义了沿主轴的对齐方式
- alignItems: 子级沿横轴的方向
- flexWrap: 子级是堆叠成单行还是多行。默认为单行。
- 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在Texture中的工作方式与在Web中CSS中的工作方式相同。例如,默认值是不同的,并且没有flex参数。有关更多信息,请参见Web Flexbox差异。
ASInsetLayoutSpec
在布局传递期间,ASInsetLayoutSpec
减去其inset之后,将其constrainedSize.max
CGSize传递给其子级。子项确定其最终尺寸后,ASInsetLayoutSpec
将通过其最终尺寸,即其子项的尺寸加上其ASInsetLayoutSpec
边距。由于ASInsetLayoutSpec
的版式规格是根据其子代的尺寸来确定的,因此子代必须具有内在的尺寸或明确设置其尺寸。
如果在UIEdgeInsets
中将INFINITY
设置为值,则插入规范将仅使用子级的固有尺寸。看到一个例子。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
...
UIEdgeInsets *insets = UIEdgeInsetsMake(10, 10, 10, 10);
ASInsetLayoutSpec *headerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:textNode];
...
}
ASOverlayLayoutSpec
ASOverlayLayoutSpec
布置其子项(蓝色),并在其顶部拉伸另一个组件作为覆盖层(红色)。
覆盖规格的尺寸是根据子级的尺寸计算得出的。在下图中,子级是蓝色层。然后,将子级的尺寸作为constrainedSize
传递给叠加布局元素(红色层)。因此,重要的是子级(蓝色层)必须具有固有大小或在其上设置的大小。
当将自动子节点管理与
ASOverlayLayoutSpec
一起使用时,节点有时会以错误的顺序出现。这是一个已知问题,将很快得到解决。当前的解决方法是手动添加节点,必须将叠加布局元素(红色)作为子节点添加到子布局元素(蓝色)之后的父节点。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
return [ASOverlayLayoutSpec overlayLayoutSpecWithChild:backgroundNode overlay:foregroundNode];
}
ASBackgroundLayoutSpec
ASBackgroundLayoutSpec
布置一个组件(蓝色),并拉伸其后的另一个组件作为背景(红色)。
背景规格的大小是根据孩子的大小计算得出的。在下图中,子级是蓝色层。然后,将孩子的尺寸作为constrainedSize
传递到背景布局元素(红色层)。因此,重要的是子级(蓝色层)必须具有固有大小或在其上设置的大小。
当将自动子节点管理与ASOverlayLayoutSpec一起使用时,节点有时会以错误的顺序出现。这是一个已知问题,将很快得到解决。当前解决方法是手动添加节点,必须将子布局元素(蓝色)作为子节点添加到子背景元素(红色)之后的父节点。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
return [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:foregroundNode background:backgroundNode];
}
注意:添加子节点的顺序对于此布局规范很重要。必须将背景对象作为子节点添加到父对象,然后再添加到前景对象。目前,使用ASM不能保证此订单!
ASCenterLayoutSpec
ASCenterLayoutSpec
将其子项置于其最大constrainedSize
之内。
如果中心规格的宽度或高度不受限制,则会缩小到子级的大小。
ASCenterLayoutSpec
具有两个属性:
- centeringOptions。确定子spec如何在中心规格内居中。选项包括:无,X,Y,XY。
- sizingOptions。确定中心规格将占用多少空间。选项包括:默认,最小X,最小Y,最小XY。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASStaticSizeDisplayNode *subnode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(70, 100));
return [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY
sizingOptions:ASCenterLayoutSpecSizingOptionDefault
child:subnode]
}
ASRatioLayoutSpec
ASRatioLayoutSpec
以固定的纵横比布局组件,该比例可以缩放. 此规范必须具有作为约束大小传递给它的宽度或高度,因为它使用此值进行缩放
使用比率规范为ASNetworkImageNode
或ASVideoNode
提供固有大小是非常普遍的,因为在内容从服务器返回之前,这两者都不具有固有大小。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
// Half Ratio
ASStaticSizeDisplayNode *subnode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(100, 100));
return [ASRatioLayoutSpec ratioLayoutSpecWithRatio:0.5 child:subnode];
}
ASRelativeLayoutSpec
对组件进行布局,并根据垂直和水平位置说明符将其放置在布局范围内。与“ 9部分”图像区域类似,可以将孩子放置在4个角的任何一个或4个边缘中的任何一个的中间以及中心。
这是一个非常强大的类,但过于复杂而无法在本概述中进行介绍。有关更多信息,请查看ASRelativeLayoutSpec
的-calculateLayoutThatFits:
方法+属性。
- (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
属性来指定其子元素的确切位置(x / y坐标)。绝对布局比其他类型的布局更不灵活且更难维护。
ASAbsoluteLayoutSpec
具有一个属性:
- sizing。确定绝对规格将占用多少空间。选项包括:“默认”和“适合大小”。请注意,“适合大小”选项将复制旧的
ASStaticLayoutSpec
的行为。
- (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
可以实现此目标.
ASCornerLayoutSpec
会很好针对自己的尺寸计算, 最好的解释情况是,在用户头像图像的一角添加一个小的徽标视图,而无需担心过少的徽标框(超出头像图像框)可能会影响整体布局尺寸, 默认情况下,仅当您手动打开wrapsCorner
属性时,才会将角元素的大小添加到布局大小中
从2.7及更高版本引入ASCornerLayoutSpec
。
ASLayoutSpec
ASLayoutSpec
是主要类,因为所有布局规范都被子类化. 它的主要工作是处理所有子代管理,但也可以用于创建自定义布局规范. 尽管只有超级高级才需要/需要创建ASLayoutSpec
的自定义子类。而是尝试使用提供的布局规格并将它们组合在一起以创建更高级的布局
当应用.flexGrow
和/或.flexShrink
时,ASLayoutSpec
的另一种用法是与其他子级一起用作ASStackLayoutSpec
中的间隔符。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
...
// ASLayoutSpec as spacer
ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
spacer.style.flexGrow = 1.0;
stack.children = @[imageNode, spacer, textNode];
...
}