一年多以前就接触了AsyncDisplayKit,但是那时菜的抠脚,不会用。现在打算学一下。
ASDK的2.0版本更名为Texture,主要做的事情就是将渲染和布局从主线程移到异步线程,充分利用多核心的优势,努力保证UI不卡顿。
这篇主要讲ASDK的布局,这个布局学习起来还是有点麻烦的,但是掌握了之后感觉用起来比AutoLayout要方便(反正我目前是没有完全掌握)。
一、ASDisplayNode
首先我们需要讲一下ASDisplayNode
这个类,这个类相当于UIView
,是ASDK中的视图的基类。
下面是ASDisplayNode
的介绍:
/**
* An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view
* hierarchy off the main thread, and could do rendering off the main thread as well.
*
* The node API is designed to be as similar as possible to `UIView`. See the README for examples.
*
* ## Subclassing
*
* `ASDisplayNode` can be subclassed to create a new UI element. The subclass header `ASDisplayNode+Subclasses` provides
* necessary declarations and conveniences.
*
* Commons reasons to subclass includes making a `UIView` property available and receiving a callback after async
* display.
*
*/
二、- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize;
这个方法是ASDisplayNode
提供的,我们实现这个方法来进行布局。
三、ASLayoutSpec(布局规则)
ASLayoutSpec
这个类是所有布局类的基类,它主要遵循了<ASLayoutElement>
这个代理,这个代理声明了@property (nonatomic, readonly) ASLayoutElementStyle *style;
这个属性,用来设置宽、高、size等属性。下面是一些ASLayoutSpec
的子类。
3.1 ASWrapperLayoutSpec
顾名思义,这个布局规则的作用就是将视图完整填充到父视图。
示例代码如下:
/**
ASWrapperLayoutSpec:填充整个视图
*/
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
return [ASWrapperLayoutSpec wrapperWithLayoutElement:self.subnode1];
}
#pragma mark - lazy load
- (ASDisplayNode *)subnode1 {
if (!_subnode1) {
_subnode1 = [[ASDisplayNode alloc] init];
_subnode1.backgroundColor = [UIColor redColor];
}
return _subnode1;
}
效果图如下:
ASWrapperLayoutSpec3.2 ASStackLayoutSpec
ASStackLayoutSpec
是最常用的布局规则,和flexbox很像(然而我并没有研究过flexbox)。下面是常用属性的介绍:
/**
ASStackLayoutSpec:最常用的类,盒子布局
1. direction:主轴的方向,有两个可选值:
纵向:ASStackLayoutDirectionVertical
横向:ASStackLayoutDirectionHorizontal
2. spacing: 主轴上视图排列的间距,比如有四个视图,那么它们之间的存在三个间距值都应该是spacing
3. justifyContent: 主轴上的排列方式,有五个可选值:
ASStackLayoutJustifyContentStart 从前往后排列
ASStackLayoutJustifyContentCenter 居中排列
ASStackLayoutJustifyContentEnd 从后往前排列
ASStackLayoutJustifyContentSpaceBetween 间隔排列,两端无间隔
ASStackLayoutJustifyContentSpaceAround 间隔排列,两端有间隔
4. alignItems: 交叉轴上的排列方式,有五个可选值:
ASStackLayoutAlignItemsStart 从前往后排列
ASStackLayoutAlignItemsEnd 从后往前排列
ASStackLayoutAlignItemsCenter 居中排列
ASStackLayoutAlignItemsStretch 拉伸排列
ASStackLayoutAlignItemsBaselineFirst 以第一个文字元素基线排列(主轴是横向才可用)
ASStackLayoutAlignItemsBaselineLast 以最后一个文字元素基线排列(主轴是横向才可用)
5. children: 包含的视图。数组内元素顺序同样代表着布局时排列的顺序,所以需要注意
*/
直接上代码:
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
self.subnode1.style.preferredSize = CGSizeMake(constrainedSize.max.width * 0.2, constrainedSize.max.height * 0.2);
self.subnode2.style.preferredSize = CGSizeMake(constrainedSize.max.width * 0.2, constrainedSize.max.height * 0.2);
self.subnode3.style.preferredSize = CGSizeMake(constrainedSize.max.width * 0.2, constrainedSize.max.height * 0.2);
ASStackLayoutSpec *horizontalStack = [[ASStackLayoutSpec alloc] init];
horizontalStack.direction = ASStackLayoutDirectionHorizontal;
horizontalStack.justifyContent = ASStackLayoutJustifyContentCenter;
horizontalStack.alignItems = ASStackLayoutAlignItemsStretch;
horizontalStack.alignContent = ASStackLayoutAlignContentEnd;
horizontalStack.flexWrap = ASStackLayoutFlexWrapWrap;
horizontalStack.spacing = 6;
horizontalStack.children = @[self.subnode1, self.subnode2];
ASStackLayoutSpec *verticalStack = [[ASStackLayoutSpec alloc] init];
verticalStack.direction = ASStackLayoutDirectionVertical;
verticalStack.justifyContent = ASStackLayoutJustifyContentCenter;
verticalStack.alignItems = ASStackLayoutAlignItemsCenter;
verticalStack.children = @[horizontalStack, self.subnode3];
verticalStack.spacing = 6;
return verticalStack;
}
#pragma mark - lazy load
- (ASDisplayNode *)subnode1 {
if (!_subnode1) {
_subnode1 = [[ASDisplayNode alloc] init];
_subnode1.backgroundColor = [UIColor redColor];
}
return _subnode1;
}
- (ASDisplayNode *)subnode2 {
if (!_subnode2) {
_subnode2 = [[ASDisplayNode alloc] init];
_subnode2.backgroundColor = [UIColor blueColor];
}
return _subnode2;
}
- (ASDisplayNode *)subnode3 {
if (!_subnode3) {
_subnode3 = [[ASDisplayNode alloc] init];
_subnode3.backgroundColor = [UIColor cyanColor];
}
return _subnode3;
}
效果图如下:
ASStackLayoutSpec3.3 ASInsetLayoutSpec
这个布局规则的作用就是相对父视图设置内边距。
代码如下:
/**
ASInsetLayoutSpec:相对于父视图边距
*/
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
UIEdgeInsets insets = UIEdgeInsetsMake(6, 6, 6, 6);
ASInsetLayoutSpec *inset = [[ASInsetLayoutSpec alloc] init];
inset.insets = insets;
inset.child = self.subnode1;
return inset;
}
#pragma mark - lazy load
- (ASDisplayNode *)subnode1 {
if (!_subnode1) {
_subnode1 = [[ASDisplayNode alloc] init];
_subnode1.backgroundColor = [UIColor redColor];
}
return _subnode1;
}
效果图如下:
ASInsetLayoutSpec3.4 ASOverlayLayoutSpec 和 ASBackgroundLayoutSpec
这两个布局规则没有特别大的作用,即使使用也不会更改视图的层级关系。代码和效果图就不提供了。
3.5 ASCenterLayoutSpec
这个类的作用是将视图按照X轴或Y轴或XY轴的中心进行布局。
代码如下:
/**
ASCenterLayoutSpec:居中布局
*/
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
self.subnode1.style.preferredSize = CGSizeMake(constrainedSize.max.width * 0.1, constrainedSize.max.height * 0.1);
ASCenterLayoutSpec *center = [[ASCenterLayoutSpec alloc] init];
center.child = self.subnode1;
center.centeringOptions = ASCenterLayoutSpecCenteringXY;
return center;
}
#pragma mark - lazy load
- (ASDisplayNode *)subnode1 {
if (!_subnode1) {
_subnode1 = [[ASDisplayNode alloc] init];
_subnode1.backgroundColor = [UIColor redColor];
}
return _subnode1;
}
效果图如下:
ASCenterLayoutSpec3.6 ASRatioLayoutSpec
这个布局规则的作用是设置自身的宽高比。
代码如下:
/**
ASRatioLayoutSpec:设置自身宽高比
*/
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
ASRatioLayoutSpec *ratio = [[ASRatioLayoutSpec alloc] init];
ratio.child = self.subnode1;
ratio.ratio = 0.5;
return [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:ratio];
}
#pragma mark - lazy load
- (ASDisplayNode *)subnode1 {
if (!_subnode1) {
_subnode1 = [[ASDisplayNode alloc] init];
_subnode1.backgroundColor = [UIColor redColor];
}
return _subnode1;
}
效果图如下:
ASRatioLayoutSpec3.7 ASRelativeLayoutSpec
相对布局有horizontalPosition
和verticalPosition
两个属性,这两个属性都提供了start
、center
、end
这三个位置,所以可以将视图布局在9个位置。
代码如下:
/**
ASRelativeLayoutSpec:相对布局
*/
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
self.subnode1.style.preferredSize = CGSizeMake(constrainedSize.max.width * 0.1, constrainedSize.max.height * 0.1);
ASRelativeLayoutSpec *relative = [[ASRelativeLayoutSpec alloc] init];
relative.child = self.subnode1;
relative.horizontalPosition = ASRelativeLayoutSpecPositionEnd;
relative.verticalPosition = ASRelativeLayoutSpecPositionCenter;
relative.sizingOption = ASRelativeLayoutSpecSizingOptionDefault;
return relative;
}
#pragma mark - lazy load
- (ASDisplayNode *)subnode1 {
if (!_subnode1) {
_subnode1 = [[ASDisplayNode alloc] init];
_subnode1.backgroundColor = [UIColor redColor];
}
return _subnode1;
}
效果图如下:
ASRelativeLayoutSpec3.8 ASAbsoluteLayoutSpec
绝对布局和设置frame很像,视图根据设置坐标和大小进行布局。
代码如下:
/**
ASAbsoluteLayoutSpec:绝对布局
和设置frame一样
*/
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
self.subnode1.style.layoutPosition = CGPointMake(0, 0);
self.subnode1.style.preferredSize = CGSizeMake(50, 50);
self.subnode2.style.layoutPosition = CGPointMake(100, 100);
self.subnode2.style.preferredSize = CGSizeMake(50, 50);
ASAbsoluteLayoutSpec *absolute = [[ASAbsoluteLayoutSpec alloc] init];
absolute.children = @[self.subnode1, self.subnode2];
return absolute;
}
#pragma mark - lazy load
- (ASDisplayNode *)subnode1 {
if (!_subnode1) {
_subnode1 = [[ASDisplayNode alloc] init];
_subnode1.backgroundColor = [UIColor redColor];
}
return _subnode1;
}
- (ASDisplayNode *)subnode2 {
if (!_subnode2) {
_subnode2 = [[ASDisplayNode alloc] init];
_subnode2.backgroundColor = [UIColor blueColor];
}
return _subnode2;
}
效果图如下:
ASAbsoluteLayoutSpec3.9 ASCornerLayoutSpec
这个布局有点像是为视图设置角标。
代码如下:
/**
ASCornerLayoutSpec:角标布局
*/
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
self.subnode1.style.preferredSize = CGSizeMake(50, 50);
self.subnode2.style.preferredSize = CGSizeMake(20, 20);
ASCornerLayoutSpec *corner = [[ASCornerLayoutSpec alloc] init];
corner.child = self.subnode1;
corner.corner = self.subnode2;
corner.cornerLocation = ASCornerLayoutLocationTopRight;
corner.offset = CGPointMake(-5, 5);
ASCenterLayoutSpec *center = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:corner];
return center;
}
#pragma mark - lazy load
- (ASDisplayNode *)subnode1 {
if (!_subnode1) {
_subnode1 = [[ASDisplayNode alloc] init];
_subnode1.backgroundColor = [UIColor redColor];
}
return _subnode1;
}
- (ASDisplayNode *)subnode2 {
if (!_subnode2) {
_subnode2 = [[ASDisplayNode alloc] init];
_subnode2.backgroundColor = [UIColor blueColor];
_subnode2.borderColor = [UIColor whiteColor].CGColor;
_subnode2.borderWidth = 3;
_subnode2.cornerRadius = 10;
}
return _subnode2;
}
效果图如下:
ASCornerLayoutSpec四、demo练习
介绍完上面基础的布局,让我们来练习练习,实现两个小demo。
4.1 demo1
首先我们来看一下效果图:
demo1简单分析一下,这个demo首先需要一张背景图在最下面填充父视图,然后图片上方的底部有两个label。
背景图我们用wrapperLayout来进行布局,两个label用stackLayout来进行布局,label的左边和底部间距使用insetLayout包一下stackLayout来实现,最后我们使用overLayout来将wrapper和insetLayout包起来。
下面是我的代码实现:
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
ASWrapperLayoutSpec *wrapper = [ASWrapperLayoutSpec wrapperWithLayoutElement:self.backgroundNode];
ASStackLayoutSpec *stack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:10 justifyContent:ASStackLayoutJustifyContentEnd alignItems:ASStackLayoutAlignItemsNotSet children:@[self.titleNode, self.subtitleNode]];
ASInsetLayoutSpec *inset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 18, 12, 0) child:stack];
ASOverlayLayoutSpec *overlay = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:wrapper overlay:inset];
return overlay;
}
4.2 demo2
我们来看一下效果图:
demo2整体看上去应该是一个方向为vertical的stackLayout,底部的效果是用stackLayout嵌套来实现的。我们直接看代码:
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
// 顶部图片宽高比
ASRatioLayoutSpec *ratio = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1 child:self.topImageNode];
// 左下角价格和销量
ASStackLayoutSpec *bottomLeftStack = [[ASStackLayoutSpec alloc] init];
bottomLeftStack.direction = ASStackLayoutDirectionHorizontal;
bottomLeftStack.justifyContent = ASStackLayoutJustifyContentStart;
bottomLeftStack.spacing = 6;
bottomLeftStack.children = @[
// 文字居中
[ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringY
sizingOptions:ASCenterLayoutSpecSizingOptionDefault
child:self.priceNode],
[ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringY
sizingOptions:ASCenterLayoutSpecSizingOptionDefault
child:self.salesNode]
];
// 底部价格、销量和更多按钮
ASStackLayoutSpec *horizontalStack = [[ASStackLayoutSpec alloc] init];
horizontalStack.direction = ASStackLayoutDirectionHorizontal;
horizontalStack.justifyContent = ASStackLayoutJustifyContentSpaceBetween;
horizontalStack.children = @[
bottomLeftStack,
self.moreButton
];
// 整体的纵向布局
ASStackLayoutSpec *verticalStack = [[ASStackLayoutSpec alloc] init];
verticalStack.direction = ASStackLayoutDirectionVertical;
verticalStack.justifyContent = ASStackLayoutJustifyContentSpaceBetween;
verticalStack.children = @[
ratio,
// 缩进
[ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 12, 0, 12) child:self.titleNode],
[ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 12, 0, 12) child:self.descNode],
[ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 12, 0, 12) child:horizontalStack]
];
// 下面留空
ASInsetLayoutSpec *inset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 0, 12, 0) child:verticalStack];
return inset;
}
总结一下,ASDK的布局规则,可能上手不是那么简单,但是掌握之后,布局起来还是比较方便的。
网友评论