一年多以前就接觸了AsyncDisplayKit,但是那時菜的摳腳疟羹,不會用。現(xiàn)在打算學(xué)一下禀倔。
ASDK的2.0版本更名為Texture榄融,主要做的事情就是將渲染和布局從主線程移到異步線程,充分利用多核心的優(yōu)勢救湖,努力保證UI不卡頓愧杯。
這篇主要講ASDK的布局,這個布局學(xué)習(xí)起來還是有點麻煩的鞋既,但是掌握了之后感覺用起來比AutoLayout要方便(反正我目前是沒有完全掌握)力九。
一、ASDisplayNode
首先我們需要講一下ASDisplayNode
這個類邑闺,這個類相當(dāng)于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
提供的抵乓,我們實現(xiàn)這個方法來進(jìn)行布局。
三、ASLayoutSpec(布局規(guī)則)
ASLayoutSpec
這個類是所有布局類的基類灾炭,它主要遵循了<ASLayoutElement>
這個代理,這個代理聲明了@property (nonatomic, readonly) ASLayoutElementStyle *style;
這個屬性茎芋,用來設(shè)置寬、高蜈出、size等屬性田弥。下面是一些ASLayoutSpec
的子類。
3.1 ASWrapperLayoutSpec
顧名思義铡原,這個布局規(guī)則的作用就是將視圖完整填充到父視圖皱蹦。
示例代碼如下:
/**
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;
}
效果圖如下:
3.2 ASStackLayoutSpec
ASStackLayoutSpec
是最常用的布局規(guī)則,和flexbox很像(然而我并沒有研究過flexbox)眷蜈。下面是常用屬性的介紹:
/**
ASStackLayoutSpec:最常用的類沪哺,盒子布局
1. direction:主軸的方向,有兩個可選值:
縱向:ASStackLayoutDirectionVertical
橫向:ASStackLayoutDirectionHorizontal
2. spacing: 主軸上視圖排列的間距酌儒,比如有四個視圖辜妓,那么它們之間的存在三個間距值都應(yīng)該是spacing
3. justifyContent: 主軸上的排列方式,有五個可選值:
ASStackLayoutJustifyContentStart 從前往后排列
ASStackLayoutJustifyContentCenter 居中排列
ASStackLayoutJustifyContentEnd 從后往前排列
ASStackLayoutJustifyContentSpaceBetween 間隔排列忌怎,兩端無間隔
ASStackLayoutJustifyContentSpaceAround 間隔排列籍滴,兩端有間隔
4. alignItems: 交叉軸上的排列方式,有五個可選值:
ASStackLayoutAlignItemsStart 從前往后排列
ASStackLayoutAlignItemsEnd 從后往前排列
ASStackLayoutAlignItemsCenter 居中排列
ASStackLayoutAlignItemsStretch 拉伸排列
ASStackLayoutAlignItemsBaselineFirst 以第一個文字元素基線排列(主軸是橫向才可用)
ASStackLayoutAlignItemsBaselineLast 以最后一個文字元素基線排列(主軸是橫向才可用)
5. children: 包含的視圖榴啸。數(shù)組內(nèi)元素順序同樣代表著布局時排列的順序孽惰,所以需要注意
*/
直接上代碼:
- (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;
}
效果圖如下:
3.3 ASInsetLayoutSpec
這個布局規(guī)則的作用就是相對父視圖設(shè)置內(nèi)邊距。
代碼如下:
/**
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;
}
效果圖如下:
3.4 ASOverlayLayoutSpec 和 ASBackgroundLayoutSpec
這兩個布局規(guī)則沒有特別大的作用鸥印,即使使用也不會更改視圖的層級關(guān)系勋功。代碼和效果圖就不提供了。
3.5 ASCenterLayoutSpec
這個類的作用是將視圖按照X軸或Y軸或XY軸的中心進(jìn)行布局库说。
代碼如下:
/**
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;
}
效果圖如下:
3.6 ASRatioLayoutSpec
這個布局規(guī)則的作用是設(shè)置自身的寬高比狂鞋。
代碼如下:
/**
ASRatioLayoutSpec:設(shè)置自身寬高比
*/
- (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;
}
效果圖如下:
3.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;
}
效果圖如下:
3.8 ASAbsoluteLayoutSpec
絕對布局和設(shè)置frame很像信不,視圖根據(jù)設(shè)置坐標(biāo)和大小進(jìn)行布局。
代碼如下:
/**
ASAbsoluteLayoutSpec:絕對布局
和設(shè)置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;
}
效果圖如下:
3.9 ASCornerLayoutSpec
這個布局有點像是為視圖設(shè)置角標(biāo)亡呵。
代碼如下:
/**
ASCornerLayoutSpec:角標(biāo)布局
*/
- (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;
}
效果圖如下:
四抽活、demo練習(xí)
介紹完上面基礎(chǔ)的布局,讓我們來練習(xí)練習(xí)政己,實現(xiàn)兩個小demo酌壕。
4.1 demo1
首先我們來看一下效果圖:
簡單分析一下掏愁,這個demo首先需要一張背景圖在最下面填充父視圖,然后圖片上方的底部有兩個label卵牍。
背景圖我們用wrapperLayout來進(jìn)行布局果港,兩個label用stackLayout來進(jìn)行布局,label的左邊和底部間距使用insetLayout包一下stackLayout來實現(xiàn)糊昙,最后我們使用overLayout來將wrapper和insetLayout包起來辛掠。
下面是我的代碼實現(xiàn):
- (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
我們來看一下效果圖:
整體看上去應(yīng)該是一個方向為vertical的stackLayout,底部的效果是用stackLayout嵌套來實現(xiàn)的释牺。我們直接看代碼:
- (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,
// 縮進(jìn)
[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;
}
總結(jié)一下,ASDK的布局規(guī)則没咙,可能上手不是那么簡單猩谊,但是掌握之后,布局起來還是比較方便的祭刚。