【翻译】Texture 布局--布局规格(Layout Specs)

这篇文章介绍如何使用 ASLayoutSpec 的各个子类组成简单或非常复杂的布局。

July 9, 2017 -
ios texture 翻译

本文翻译自 TextureLayout Specs 一文。

布局规格


下面这些 ASLayoutSpec 的子类可以用来组成简单或非常复杂的布局。

你也可以子类化 ASLayoutSpec 来制定你自己的自定义布局规格。

ASWrapperLayoutSpec

ASWrapperLayoutSpec 是一个简单的 ASLayoutSpec 的子类,可以包装 ASLayoutElement 和根据布局元素上设置的大小来计算子元素的布局。

ASWrapperLayoutSpec 是轻易地从 -layoutSpecThatFits: 返回一个单独子 node 的理想方式。可选项,这个子 node 可自设大小信息。然而,如果你需要给尺寸设置额外的位置,请使用 ASAbsoluteLayoutSpec 来替代。

Swift Objective-C
// 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 容器)

在 Texture 全部的 layoutSpecs 里,ASStackLayoutSpec 是最有用和最强大的。ASStackLayoutSpec 使用 flexbox 的算法来确定子元素的位置和大小。Flexbox 被设计为在不同屏幕大小上提供一致的布局。在堆叠布局中,你可以在横向堆或纵向堆里对齐项目。一个堆叠布局可以是另一个堆叠布局的子项,这让利用堆叠布局规格有可能创造出几乎任何布局。

ASStackLayoutSpec 除了 <ASLayoutElement> 的属性之外,额外还有 7 个属性:

  • 方向(direction)。子元素堆叠的具体方向。如果设置了 horizontalAlignment 和 verticalAlignment,它们将被再次解析,会造成 justifyContent 和 alignItems 相应的被更新。
  • 空间(spacing)。每个子元素之间的空白量。
  • 横向对齐(horizontalAlignment)。指定子元素如何横向对齐。依赖堆叠的方向,设置对齐引起的 justifyContent 或 alignItems 的被更新。未来方向改变后,对齐方式依然有效。
  • 纵向对齐(verticalAlignment)。指定子元素如何纵向对齐。依赖堆叠的方向,设置对齐引起的 justifyContent 或 alignItems 的被更新。未来方向改变后,对齐方式依然有效。
  • 内容的合理性(justifyContent)。每个子元素之间的空白量。
  • 对齐基准(alignItems)。子元素沿横轴的方向。
  • flex 包装(flexWrap)。子元素是否被堆叠成一行还是多行。默认是单独的一行。
  • 内容对齐方式(alignContent)。如果有多行,多行沿横轴的方向。
Swift Objective-C
- (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 在减掉它的插入值之后,会传递它的 constrainedSize.max CGSize 给它的子元素。一旦子元素确定了它的最终大小,插入规格将向上传递由子元素的大小加上插入边界的最终大小。既然插入布局规格的大小是基于它的子元素的大小,子元素必须有固定大小或明确的设置它的大小。

ASInsetLayoutSpec-diagram

如果你设置 UIEdgeInsets 里的值为INFINITY,插入规格将只使用子元素的固定大小。请看这个例子

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

ASOverlayLayoutSpec

ASOverlayLayoutSpec 布局它的子元素(蓝色),拉伸其它组件叠加在它之上(红色)。

ASOverlayLayouSpec-diagram

叠加规格的大小计算自它的子元素的大小。在上图中,蓝色层是子元素。子元素的大小通过 constrainedSize 传递给叠加层元素(红色层)。因此,重要的是子元素(蓝色层)必须有一个固定的大小或于其上设置了大小。

在使用自动子 node 管理和 ASOverlayLayoutSpec 时,node 可能有时以错误的顺序出现。这是一个已知的问题,很快会修复。目前的解决方法是手动添加 node,在子布局元素(蓝色)之后,叠加布局层(红色)必须作为子 node 添加到父 node。

Swift Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
  ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
  ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
  return [ASOverlayLayoutSpec overlayLayoutSpecWithChild:backgroundNode overlay:foregroundNode];
}

ASBackgroundLayoutSpec

ASBackgroundLayoutSpec 布局一个组件(蓝色),拉伸它下面的其它组件作为背景(红色)。

ASBackgroundLayoutSpec-diagram

背景规格的大小计算自子元素的大小。在上图中,蓝色层是子元素。子元素的大小通过 constrainedSize 传递到背景布局元素(红色层)。因此,重要的是子元素(蓝色层)必须有一个固定的大小或于其上设置了大小。

在使用自动子 node 管理和 ASOverlayLayoutSpec 时,node 可能有时以错误的顺序出现。这是一个已知的问题,很快会修复。目前的解决方法是手动添加 node,在子背景元素(红色)之后,子布局元素(蓝色)必须作为子 node 添加到父 node。

Swift Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
  ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
  ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);

  return [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:foregroundNode background:backgroundNode];
}

注意:添加子 node 的顺序对于此布局规格很重要;背景对象必须在前景对象之前作为子 node 添加到父 node。使用 ASM(自动子 node 管理) 目前不能保证这个顺序!

ASCenterLayoutSpec

ASCenterLayoutSpec 按内部最大 constrainedSize(约束尺寸) 来居中它的子元素。

ASCenterLayoutSpec-diagram

如果居中规格的宽度或高度是不受约束的,它将收缩子元素的大小。

ASCenterLayoutSpec 有两个属性:

  • 居中选项(centeringOptions)。确定子元素在居中规格里如何居中。选项包括:无(None),X,Y,XY。
  • 大小选项(sizingOptions)。确定居中规格将占据多少空间。选项包括:默认(Default),最小 X 值(minimum X),最小 Y 值(minimum Y),最小 XY 值(minimum XY)。
Swift Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
  ASStaticSizeDisplayNode *subnode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(70, 100));
  return [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY
                                                    sizingOptions:ASCenterLayoutSpecSizingOptionDefault
                                                            child:subnode]
}

ASRatioLayoutSpec

ASRatioLayoutSpec 以可缩放的固定宽高比来布局一个组件。这个规格必须被传递一个宽度或高度作为约束尺寸(constrainedSize),用这个值来缩放它自身。

ASRatioLayoutSpec-diagram

经常使用比例规格来提供固有大小给 ASNetworkImageNodeASVideoNode,因为在内容从服务器返回前,它们都没有固有大小。

Swift Objective-C
- (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: 方法和属性。

Swift Objective-C
- (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)。确定绝对规格将占据多少空间。选项包括:默认(Default),和大小合适(Size to Fit)。注意大小合适选项将复制旧的 ASStaticLayoutSpec 的行为。
Swift Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
  CGSize maxConstrainedSize = constrainedSize.max;

  // Layout all nodes absolute in a static layout spec
  guitarVideoNode.layoutPosition = CGPointMake(0, 0);
  guitarVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width, maxConstrainedSize.height / 3.0));

  nicCageVideoNode.layoutPosition = CGPointMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0);
  nicCageVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0));

  simonVideoNode.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height - (maxConstrainedSize.height / 3.0));
  simonVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width/2, maxConstrainedSize.height / 3.0));

  hlsVideoNode.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height / 3.0);
  hlsVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0));

  return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[guitarVideoNode, nicCageVideoNode, simonVideoNode, hlsVideoNode]];
}

ASLayoutSpec

ASLayoutSpec 是所以那些布局规格子类的主类。它的主要工作是处理所有子元素的管理,但也能用来创建自定义布局规格。虽然只有超级先进(的页面布局)才想/需要创建 ASLayoutSpec 的自定义子类。作为替代,尝试使用已提供的布局规格并将它们组合在一起来创建更多高级布局。

ASLayoutSpec 的另一个用途是用作与其它子元素的 ASStackLayoutSpec 之间的空间,应用于 .flexGrow 和/或 .flexShrink

Swift Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
  ...
  // ASLayoutSpec as spacer
  ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
  spacer.flexGrow = true;

  stack.children = @[imageNode, spacer, textNode];
  ...
}