ASDK的Layout API(六)

一: Layout Element Properties(布局元素属性)

  • ASStackLayoutElement属性 - 仅对作为堆栈规范的子节点或布局规范生效
  • ASAbsoluteLayoutElement属性 - 仅对绝对规格的子节点或布局规范生效
  • ASLayoutElement属性 - 适用于所有节点和布局规格
1.1: ASStackLayoutElement属性

Please note that the following properties will only take effect if set on the child of anSTACK layout spec.

Property Description
CGFloat .style.spacingBefore 在堆叠方向上放置此对象之前需要额外的空间。
CGFloat .style.spacingAfter 在堆叠方向上放置此对象之后需要额外的空间。
CGFloat .style.flexGrow 如果子布局堆叠尺寸的总和小于最小尺寸,该物体是否应该增长?
CGFloat .style.flexShrink 如果子布局的堆栈尺寸总和大于最大尺寸,这个对象是否应该缩小?
ASDimension .style.flexBasis 在应用flexGrow /flexShrink属性并分配剩余空间之前,在堆栈维度(水平或垂直)中指定此对象的初始大小。
ASStackLayoutAlignSelf .style.alignSelf 对象沿着横轴取向,覆盖alignItems。选项包括:- (1)ASStackLayoutAlignSelfAuto(2)ASStackLayoutAlignSelfStart、(3)ASStackLayoutAlignSelfEnd、(4)ASStackLayoutAlignSelfCenter、(5)ASStackLayoutAlignSelfStretch
CGFloat .style.ascender 用于基线对齐。从物体顶部到其基线的距离。
CGFloat .style.descender 用于基线对齐。物体底部到底部的距离。
1.2: ASAbsoluteLayoutElement属性

Please note that the following properties will only take effect if set on the child of anABSOLUTE layout spec.

Property Description
CGPoint .style.layoutPosition 该对象的CGPoint在其ASAbsoluteLayoutSpec`父规范中的位置
1.3: ASLayoutElement属性

请注意,以下属性适用于所有布局元素。

Property Description
ASDimension.style.width width属性指定了ASLayoutElement的内容区域的宽度。 minWidthmaxWidth属性覆盖width。默认为ASDimensionAuto
ASDimension.style.height height属性指定了ASLayoutElement的内容区域的高度。 minHeightmaxHeight属性覆盖height。默认为ASDimensionAuto
ASDimension.style.minWidth minWidth属性用于设置给定元素的最小宽度。 它可以防止width属性的使用值变得小于minWidth指定的值。 minWidth的值覆盖maxWidthwidth。 默认为ASDimensionAuto
ASDimension.style.maxWidth maxWidth属性用于设置给定元素的最大宽度。 它可以防止width属性的使用值变得大于为maxWidth指定的值。 “maxWidth”的值覆盖了“width”,但“minWidth”覆盖了“maxWidth”。 默认为ASDimensionAuto
ASDimension.style.minHeight minHeight属性用于设置给定元素的最小高度。 它可以防止height属性的使用值变得小于minHeight指定的值。 minHeight的值覆盖maxHeightheight。 默认为ASDimensionAuto
ASDimension .style.maxHeight 'maxHeight属性用于设置给定元素的最大高度。 它可以防止height属性的使用值变得大于为maxHeight指定的值。maxHeight的值覆盖height,但是minHeight覆盖maxHeight。 默认为ASDimensionAuto`
CGSize .style.preferredSize 为布局元素提供建议大小。 如果提供了可选的minSize或maxSize,并且preferredSize超过了这些值,则minSize或maxSize将被强制执行。 如果未提供此可选值,则布局元素的大小将默认为提供的内部大小calculateSizeThatFits, 此方法是可选的,但preferredSize或preferredLayoutSize中的一个对于没有内在内容大小的节点或者应以与内在大小不同的大小布置的节点是必需的。 例如,可以在ASImageNode上将此属性设置为以与底层图像大小不同的大小显示。警告:当尺寸的宽度或高度相对时调用getter将导致断言。
CGSize .style.minSize 为布局元素提供最小大小限制的可选属性。 如果提供,则此限制将始终强制执行。 如果父级布局元素的最小大小小于其子级的最小大小,则会强制执行子级的最小大小,并且其大小将扩展到布局规格之外。例如,如果您在全屏容器中的某个元素上设置50%的首选相对宽度和200点的最小宽度,则这会导致iPhone屏幕上的宽度为160点。 但是,由于160分低于200分的最小宽度,因此将使用最小宽度。
CGSize .style.maxSize 为布局元素提供最大大小限制的可选属性。 如果提供,则此限制将始终强制执行。 如果子布局元素的最大尺寸小于其父元素,则会强制执行子元素的最大尺寸,并且其尺寸将超出布局规范。 例如,如果您在全屏容器中将元素的首选相对宽度设置为50%,最大宽度设置为120点,则这会导致iPhone屏幕上的宽度为160点。 但是,由于160分高于120分的最大宽度,因此将使用最大宽度。
ASLayoutSize.style.preferredLayoutSize 为布局元素提供建议的RELATIVE大小。 ASLayoutSize使用百分比而不是点来指定布局。 例如。 宽度应该是父宽度的50%。 如果提供了可选的minLayoutSizemaxLayoutSize,并且preferredLayoutSize超过了这些值,则minLayoutSizemaxLayoutSize将被强制执行。 如果未提供此可选值,则布局元素的大小将默认为其提供的“内部内容大小” calculateSizeThatFits
ASLayoutSize.style.minLayoutSize 一个可选属性,为布局元素提供了一个最小的RELATIVE大小。 如果提供,则此限制将始终强制执行。 如果父级布局元素的最小相对大小小于其子级的最小相对大小,则会强制执行子级的最小相对大小,并且其大小将扩展到布局规范之外。
ASLayoutSize.style.maxLayoutSize 一个可选属性,为布局元素提供了最大的RELATIVE大小。 如果提供,则此限制将始终强制执行。 如果父级布局元素的最大相对大小小于其子级的最大相对大小,则会强制执行子级的最大相对大小,并且其大小将扩展到布局规范之外。

二: Layout API Sizing

理解Layout API中复合维度类型的最简单方法是查看所有单位的相互关系。


image
2.1: Values (CGFloat, ASDimension)

ASDimension基本上是一个正常的CGFloat,支持表示点值,相对百分比值或自动值。
该单元允许相同的API采用固定值以及相关值。

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

// dimension returned in points
ASDimensionMake(@"70pt");
ASDimensionMake(70);      
ASDimensionMakeWithPoints(70);
swift
// dimension returned is relative (%)
ASDimensionMake("50%")
ASDimensionMakeWithFraction(0.5)

// dimension returned in points
ASDimensionMake("70pt")
ASDimensionMake(70)
ASDimensionMakeWithPoints(70)
2.2: 使用ASDimension的示例

ASDimension用于在ASStackLayoutSpec的子级上设置flexBasis属性。 flexBasis属性指定对象在堆栈维度中的初始大小,其中堆栈维度是它是水平还是垂直堆栈。

在下面的视图中,我们希望左堆占据水平宽度的40%,右堆占据宽度的60%。


image

我们通过在水平堆栈的两个childen上设置.flexBasis属性来做到这一点:


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

horizontalStack.children = [self.leftStack, self.rightStack]
2.3: Sizes (CGSize, ASLayoutSize)

ASLayoutSize类似于CGSize,但其宽度和高度值可以表示点或百分比值。宽度和高度的类型是独立的;任何一个都可能是一个点或百分比值。

OC:
ASLayoutSizeMake(_ width: ASDimension, _ height: ASDimension)
swift:
ASLayoutSizeMake(_ width: ASDimension, _ height: ASDimension)

ASLayoutSize用于设置布局元素的.preferredLayoutSize.minLayoutSize.maxLayoutSize属性。它允许相同的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);

如果您不需要相对值,则可以设置布局元素的.preferredSize.minSize.maxSize属性。这些属性采用常规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%");
2.4: Size Range (ASSizeRange)

UIKit不提供捆绑最小和最大CGSize的结构。所以,创建ASSizeRange以支持最小和最大CGSize对。

ASSizeRange主要用于布局API的内部。但是,作为layoutSpecThatFits的输入传递的constrainedSize值是一个ASSizeRange

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

传递给ASDisplayNode子类的layoutSpecThatFits方法的constrainedSize是节点应该适应的最小和最大尺寸。包含在constrainedSize中的最小和最大CGSizes可用于调整节点的布局元素的大小。

三: Layout Transition API

布局转换API旨在使所有带纹理的动画变得简单 - 甚至将整个视图转换为完全不同的视图!

有了这个系统,你只需指定所需的布局,Texture将完成工作,从而找出与当前布局的差异。 它会自动添加新元素,删除transiton之后的不需要的元素,并更新任何现有元素的位置。

也有易于使用的API,可以让您完全自定义新引入的元素的起始位置,以及已移除元素的结束位置。

Use of Automatic Subnode Management is required to use the Layout Transition API.

3.1: 布局之间的动画

布局转换API可以很容易地在节点生成的布局之间进行动画,以响应节点中的某些内部状态更改。

想象一下,当您点击下一个按钮时,您想要实现此注册表单并在新字段中生成动画:

image

实现这一点的标准方法是创建一个名为SignupNode的容器节点,其中包含两个可编辑的文本字段节点和一个按钮节点作为子节点。 我们将在SignupNode上包含一个名为fieldState的属性,用于选择在节点计算其布局时显示哪个可编辑的文本字段节点。

SignupNode容器的内部布局规格如下所示:

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
  FieldNode *field;
  if (self.fieldState == SignupNodeName) {
    field = self.nameField;
  } else {
    field = self.ageField;
  }

  ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init];
  [stack setChildren:@[field, self.buttonNode]];

  UIEdgeInsets insets = UIEdgeInsetsMake(15.0, 15.0, 15.0, 15.0);
  return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:stack];
}

为了在本例中触发从nameField到ageField的转换,我们将更新SignupNode的.fieldState属性,并使用transitionLayoutWithAnimation开始转换。

此方法将使当前计算的布局无效,并重新计算现在在堆栈中的ageField的新布局。

self.signupNode.fieldState = SignupNodeAge;

[self.signupNode transitionLayoutWithAnimation:YES];

在此API的默认实现中,布局将重新计算新布局,并使用其子布局来在不使用动画的情况下确定SignupNode的子节点的大小和位置。 该API的未来版本可能会包含布局之间的默认动画,我们欢迎您提供有关您想要在此处看到的内容的反馈。 但是,我们需要实现一个自定义动画块来处理注册表单的情况。

下面的例子代表了animateLayoutTransition的覆盖:在SignupNode中。

在通过transitionLayoutWithAnimation计算新布局之后调用此方法:在实现中,我们将根据动画触发前设置的fieldState属性执行特定的动画。

- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
{
  if (self.fieldState == SignupNodeName) {
    CGRect initialNameFrame = [context initialFrameForNode:self.ageField];
    initialNameFrame.origin.x += initialNameFrame.size.width;
    self.nameField.frame = initialNameFrame;
    self.nameField.alpha = 0.0;
    CGRect finalAgeFrame = [context finalFrameForNode:self.nameField];
    finalAgeFrame.origin.x -= finalAgeFrame.size.width;
    [UIView animateWithDuration:0.4 animations:^{
      self.nameField.frame = [context finalFrameForNode:self.nameField];
      self.nameField.alpha = 1.0;
      self.ageField.frame = finalAgeFrame;
      self.ageField.alpha = 0.0;
    } completion:^(BOOL finished) {
      [context completeTransition:finished];
    }];
  } else {
    CGRect initialAgeFrame = [context initialFrameForNode:self.nameField];
    initialAgeFrame.origin.x += initialAgeFrame.size.width;
    self.ageField.frame = initialAgeFrame;
    self.ageField.alpha = 0.0;
    CGRect finalNameFrame = [context finalFrameForNode:self.ageField];
    finalNameFrame.origin.x -= finalNameFrame.size.width;
    [UIView animateWithDuration:0.4 animations:^{
      self.ageField.frame = [context finalFrameForNode:self.ageField];
      self.ageField.alpha = 1.0;
      self.nameField.frame = finalNameFrame;
      self.nameField.alpha = 0.0;
    } completion:^(BOOL finished) {
      [context completeTransition:finished];
    }];
  }
}

此方法中传递的ASContextTransitioning上下文对象包含相关信息,可帮助您确定转换前后的节点状态。 它将getter包括到旧的和新的约束大小,插入和移除节点,甚至包括原始的旧的和新的ASLayout对象。 在SignupNode示例中,我们使用它来确定每个字段的框架并在不合适的位置为它们设置动画。

一旦动画完成,就必须调用completeTransition在上下文对象上,因为它将执行必要的内部步骤,使新计算的布局成为当前的calculateLayout

请注意,在转换过程中没有使用addSubnoderemoveFromSupernode。 Texture的布局转换API分析旧布局和新布局之间节点层次结构的差异,通过自动子节点管理隐式执行节点插入和删除。

在调用animateLayoutTransition之前插入节点,这是在开始动画之前手动管理层次结构的好地方。 在didCompleteLayoutTransition中执行删除:在上下文对象上调用completeTransition之后。 如果您需要手动执行删除操作,请覆盖didCompleteLayoutTransition并执行您的自定义操作。 请注意,这将覆盖默认行为,建议您调用super或遍历上下文对象中removedSubnodes getter来执行清理。

将NO传递给transitionLayoutWithAnimation仍将贯穿animateLayoutTransitiondidCompleteLayoutTransition[context isAnimated]属性设置为NO的实现。 这是您如何处理此案的选择 - 如果有的话。 提供默认实现的简单方法是调用super

- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
{
  if ([context isAnimated]) {
    // perform animation
  } else {
    [super animateLayoutTransition:context];
  }
}
3.2: 动画约束大小更改

有时您只需要响应对节点的边界更改并为重新计算其布局设置动画。 要处理这种情况,请在节点上调用transitionLayoutWithSizeRange:animated

此方法类似于transitionLayoutWithAnimation,但如果传递的ASSizeRange等于当前的constrainedSizeForCalculatedLayout值,则不会触发动画。 这对于响应旋转事件和查看控制器大小更改非常有用:

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
  [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
  [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
    [self.node transitionLayoutWithSizeRange:ASSizeRangeMake(size, size) animated:YES];
  } completion:nil];
}

使用布局转换API的示例

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

推荐阅读更多精彩内容