官方文档连接: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
发生变化时很是有用。