Texture
拥有自己的一套成熟布局方案,虽然学习成本略高,但至少比原生的 AutoLayout
写起来舒服,重点是性能远好于 AutoLayout
, Texture
文档上也指出了这套布局方案的的优点:
- Fast : As fast as manual layout code and significantly faster than Auto Layout
- Asynchronous & Concurrent : Layouts can be computed on background threads so user interactions are not interrupted.
- Declarative : Layouts are declared with immutable data structures. This makes layout code easier to develop, document, code review, test, debug, profile, and maintain.
- Cacheable : Layout results are immutable data structures so they can be precomputed in the background and cached to increase user perceived performance.
- Extensible : Easy to share code between classes.
首先这套布局都是基于 Texture
组件的,所以当遇到要使用原生控件时,通过用 block 的方式包装一个原生组件再合适不过了,例如:
ASDisplayNode *animationImageNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{ FLAnimatedImageView *animationImageView = [[FLAnimatedImageView alloc] init]; animationImageView.layer.cornerRadius = 2.0f; animationImageView.clipsToBounds = YES; return animationImageView; }]; [self addSubnode:animationImageNode]; self.animationImageNode = animationImageNode;
ASDisplayNode
在初始化之后会检查是否有子视图,如果有就会调用
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
方法进行布局,所以对视图进行布局需要重写这个方法。看一个例子:
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{ ASInsetLayoutSpec *inset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:_childNode]; return insetLayout; }
_childNode
相对于父视图边距都为 0,也就是 AutoLayout
中 top
bottom
left
right
都为 0。
-----------------------------父视图---------------------------- | -------------------------_childNode--------------------- | | | | | | | | | | --------------------------- --------------------------- | --------------------------------------------------------------
可以看到 layoutSpecThatFits:
方法返回的必须是 ASLayoutSpec
, ASInsetLayoutSpec
是它的子类之一,下面是所有的子类及其关系:
- ASLayoutSpec
- ASAbsoluteLayoutSpec // 绝对布局
- ASBackgroundLayoutSpec // 背景布局
- ASInsetLayoutSpec // 边距布局
- ASOverlayLayoutSpec // 覆盖布局
- ASRatioLayoutSpec // 比例布局
- ASRelativeLayoutSpec // 顶点布局
- ASCenterLayoutSpec // 居中布局
- ASStackLayoutSpec // 盒子布局
- ASWrapperLayoutSpec // 填充布局
ASAbsoluteLayoutSpec
使用方法和原生的绝对布局类似
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{ self.childNode.style.layoutPosition = CGPointMake(100, 100); self.childNode.style.preferredLayoutSize = ASLayoutSizeMake(ASDimensionMake(100), ASDimensionMake(100)); ASAbsoluteLayoutSpec *absoluteLayout = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[self.childNode]]; return absoluteLayout; }
值得提的是:ASAbsoluteLayoutSpec 一般情况都会通过 ASOverlayLayoutSpec
或 ASOverlayLayoutSpec
着陆。举个例子当视图中只有一个控件需要用的是 ASAbsoluteLayoutSpec
布局,而其他控件布局用的是 ASStackLayoutSpec
(后面会介绍),那么一旦 absoluteLayout 被加入到 ASStackLayoutSpec
也就失去它原本的布局的意义。
ASOverlayLayoutSpec *contentLayout = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:stackLayout overlay:absoluteLayout];
不过官方文档明确指出应该尽量少用这种布局方式:
Absolute layouts are less flexible and harder to maintain than other types of layouts.
ASBackgroundLayoutSpec
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{ ASBackgroundLayoutSpec *backgroundLayout = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:self.childNodeB background:self.childNodeA]; return backgroundLayout; }
把 childNodeA
做为 childNodeB
的背景,也就是 childNodeB
在上层,要注意的是 ASBackgroundLayoutSpec
事实上根本不会改变视图的层级关系,比如:
ASDisplayNode *childNodeB = [[ASDisplayNode alloc] init]; childNodeB.backgroundColor = [UIColor blueColor]; [self addSubnode:childNodeB]; self.childNodeB = childNodeB; ASDisplayNode *childNodeA = [[ASDisplayNode alloc] init]; childNodeA.backgroundColor = [UIColor redColor]; [self addSubnode:childNodeA]; self.childNodeA = childNodeA;
那么即使使用上面的布局方式, childNodeB
依然在下层。它和之后会说到的 ASOverlayLayoutSpec
实际上更多的用来组合两个 Element
而已。
ASInsetLayoutSpec
比较常用的一个类,看图应该能一目了然(图片来自于 官方文档 )
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{ ASInsetLayoutSpec *inset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:_childNode]; return insetLayout; }
_childNode
相对于父视图边距都为 0,相当于填充整个父视图。
ASOverlayLayoutSpec
参考 ASBackgroundLayoutSpec
ASRatioLayoutSpec
(图片来自于 官方文档 )
也是比较常用的一个类,作用是设置自身的高宽比,例如设置正方形的视图
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{ ASRatioLayoutSpec *ratioLayout = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1.0f child:self.childNodeA]; return ratioLayout; }
ASRelativeLayoutSpec
把它称为 顶点布局 可能有点不恰当,实际上它可以把视图布局在: 左上
、 左下
、 右上
、 右下
四个顶点以外,还可以设置成居中布局。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{ self.childNodeA.style.preferredSize = CGSizeMake(100, 100); ASRelativeLayoutSpec *relativeLayout = [ASRelativeLayoutSpec relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionEnd verticalPosition:ASRelativeLayoutSpecPositionStart sizingOption:ASRelativeLayoutSpecSizingOptionDefault child:self.childNodeA]; return relativeLayout; }
上面的例子就是把 childNodeA
显示在右上角。
ASCenterLayoutSpec
绝大多数情况下用来居中显示视图
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{ self.childNodeA.style.preferredSize = CGSizeMake(100, 100); ASCenterLayoutSpec *relativeLayout = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:self.childNodeA]; return relativeLayout; }
ASStackLayoutSpec
可以说这是 最常用的类 ,而且相对于其他类来说在功能上是最接近于 AutoLayout
的。 之所以称之为 盒子布局 是因为它和 CSS 中 Flexbox
很相似,不清楚 Flexbox
的可以先看下阮一峰的这篇 博客 。
先看一个例子:
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{ self.childNodeA.style.preferredSize = CGSizeMake(100, 100); self.childNodeB.style.preferredSize = CGSizeMake(200, 200); ASStackLayoutSpec *stackLayout = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:12 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart children:@[self.childNodeA, self.childNodeB]]; return stackLayout; }
简单的说明下各个参数的作用:
-
direction
:主轴的方向,有两个可选值:- 纵向:
ASStackLayoutDirectionVertical
- 横向:
ASStackLayoutDirectionHorizontal
- 纵向:
-
spacing
: 主轴上视图排列的间距,比如有四个视图,那么它们之间的存在三个间距值都应该是spacing
-
justifyContent
: 主轴上的排列方式,有五个可选值:-
ASStackLayoutJustifyContentStart
从前往后排列 -
ASStackLayoutJustifyContentCenter
居中排列
-
ASStackLayoutJustifyContentEnd
从后往前排列 -
ASStackLayoutJustifyContentSpaceBetween
间隔排列,两端无间隔 -
ASStackLayoutJustifyContentSpaceAround
间隔排列,两端有间隔
-
-
alignItems
: 交叉轴上的排列方式,有五个可选值:-
ASStackLayoutAlignItemsStart
从前往后排列 -
ASStackLayoutAlignItemsEnd
从后往前排列
-
ASStackLayoutAlignItemsCenter
居中排列 -
ASStackLayoutAlignItemsStretch
拉伸排列 -
ASStackLayoutAlignItemsBaselineFirst
以第一个文字元素基线排列(主轴是横向才可用) -
ASStackLayoutAlignItemsBaselineLast
以最后一个文字元素基线排列(主轴是横向才可用)
-
-
children
: 包含的视图。数组内元素顺序同样代表着布局时排列的顺序,所以需要注意
主轴的方向设置尤为重要,如果主轴设置的是 ASStackLayoutDirectionVertical
, 那么 justifyContent
各个参数的意义就是:
-
ASStackLayoutJustifyContentStart
从上往下排列 -
ASStackLayoutJustifyContentCenter
居中排列 -
ASStackLayoutJustifyContentEnd
从下往上排列 -
ASStackLayoutJustifyContentSpaceBetween
间隔排列,两端无间隔 -
ASStackLayoutJustifyContentSpaceAround
间隔排列,两端有间隔
alignItems
就是:
-
ASStackLayoutAlignItemsStart
从左往右排列 -
ASStackLayoutAlignItemsEnd
从右往左排列 -
ASStackLayoutAlignItemsCenter
居中排列 -
ASStackLayoutAlignItemsStretch
拉伸排列 -
ASStackLayoutAlignItemsBaselineFirst
无效 -
ASStackLayoutAlignItemsBaselineLast
无效
对于子视图间距不一样的布局方法,后面实战中会讲到。
ASWrapperLayoutSpec
填充整个视图
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{ ASWrapperLayoutSpec *wrapperLayout = [ASWrapperLayoutSpec wrapperWithLayoutElement:self.childNodeA]; return wrapperLayout; }
布局实战
案例一
简单的文件覆盖在图片上,文字居中。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{ ASWrapperLayoutSpec *wrapperLayout = [ASWrapperLayoutSpec wrapperWithLayoutElement:self.coverImageNode]; ASCenterLayoutSpec *centerSpec = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:self.textNode]; ASOverlayLayoutSpec *overSpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:wrapperLayout overlay:centerSpec]; return overSpec; }
-
ASWrapperLayoutSpec
把图片铺满整个视图 -
ASCenterLayoutSpec
把文字居中显示 -
ASOverlayLayoutSpec
把文字覆盖到图片上
注意第三步就是之前提到的 ASOverlayLayoutSpec
/ ASBackgroundLayoutSpec
的作用:用于组合两个 Element
。
案例二
这个是 轻芒阅读 (豌豆荚一览) APP 内 AppSo 频道 Cell 的布局,应该也是比较典型的布局之一。为了方便理解先给各个元素定一下名称,从上至下,从左往右分别是:
- coverImageNode // 大图
- titleNode // 标题
- subTitleNode // 副标题
- dateTextNode // 发布时间
- shareImageNode // 分享图标
- shareNumberNode // 分享数量
- likeImageNode // 喜欢图标
- likeNumberNode // 喜欢数量
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{ self.shareImageNode.style.preferredSize = CGSizeMake(15, 15); self.likeImageNode.style.preferredSize = CGSizeMake(15, 15); ASStackLayoutSpec *likeLayout = [ASStackLayoutSpec horizontalStackLayoutSpec]; likeLayout.spacing = 4.0; likeLayout.justifyContent = ASStackLayoutJustifyContentStart; likeLayout.alignItems = ASStackLayoutAlignItemsCenter; likeLayout.children = @[self.likeImageNode, self.likeNumberNode]; ASStackLayoutSpec *shareLayout = [ASStackLayoutSpec horizontalStackLayoutSpec]; shareLayout.spacing = 4.0; shareLayout.justifyContent = ASStackLayoutJustifyContentStart; shareLayout.alignItems = ASStackLayoutAlignItemsCenter; shareLayout.children = @[self.shareImageNode, self.shareNumberNode]; ASStackLayoutSpec *otherLayout = [ASStackLayoutSpec horizontalStackLayoutSpec]; otherLayout.spacing = 12.0; otherLayout.justifyContent = ASStackLayoutJustifyContentStart; otherLayout.alignItems = ASStackLayoutAlignItemsCenter; otherLayout.children = @[likeLayout, shareLayout]; ASStackLayoutSpec *bottomLayout = [ASStackLayoutSpec horizontalStackLayoutSpec]; bottomLayout.justifyContent = ASStackLayoutJustifyContentSpaceBetween; bottomLayout.alignItems = ASStackLayoutAlignItemsCenter; bottomLayout.children = @[self.dateTextNode, otherLayout]; self.titleNode.style.spacingBefore = 12.0f; self.subTitleNode.style.spacingBefore = 16.0f; self.subTitleNode.style.spacingAfter = 20.0f; ASRatioLayoutSpec *rationLayout = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:0.5 child:self.coverImageNode]; ASStackLayoutSpec *contentLayout = [ASStackLayoutSpec horizontalStackLayoutSpec]; contentLayout.justifyContent = ASStackLayoutJustifyContentStart; contentLayout.alignItems = ASStackLayoutAlignItemsStretch; contentLayout.children = @[ rationLayout, self.titleNode, self.subTitleNode, bottomLayout ]; ASInsetLayoutSpec *insetLayout = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(16, 16, 16, 16) child:contentLayout]; return insetLayout; }
下面详细解释下为什么这么布局,不过首先要明确的是,Texture 的这套布局方式必须遵守 从里到外 的布局原则,使用起来才会得心应手。
- 根据布局的原则,首先利用
ASStackLayoutSpec
布局分享图标
和分享数量
、喜欢图标
和喜欢数量
。 - 还是通过
ASStackLayoutSpec
包装第一步的两个的布局得到otherLayout
布局对象。 - 依然是
ASStackLayoutSpec
包装otherLayout
和发布时间
。注意这里设置横向的排列方式ASStackLayoutJustifyContentSpaceBetween
已到达两端布局的目的,最终返回bottomLayout
。 - 由于
大图
是网络图片,对于 Cell 来说,子视图的布局必能能决定其高度(Cell 宽度是默认等于 TableNode 的宽度),所以这里必须设置大图
的高度,ASRatioLayoutSpec
设置了图片的高宽比。 - 接下来布局应该就是
大图
、标题
、副标题
、bottomLayout
的一个纵向布局,可以发现这里的视图间距并不相同,这时候spacingBefore
和spacingAfter
就会很有用,它们用来分别设置元素在主轴上的前后间距。self.titleNode.style.spacingBefore = 12.0f;
意思就是标题
相对于大图
间距为 12。 - 最后通过一个
ASInsetLayoutSpec
设置一个边距。
可以看到不仅是 Node
, ASLayoutSpec
本身也可以作为布局元素,这是因为只要是遵守了 <ASLayoutElement>
协议的对象都可以作为布局元素。
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。