美文网首页Texture
AsyncDisplayKit学习系列1 - 布局

AsyncDisplayKit学习系列1 - 布局

作者: 柚丸 | 来源:发表于2019-03-27 11:14 被阅读0次

    一年多以前就接触了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;
    }
    

    效果图如下:

    ASWrapperLayoutSpec

    3.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;
    }
    

    效果图如下:

    ASStackLayoutSpec

    3.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;
    }
    

    效果图如下:

    ASInsetLayoutSpec

    3.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;
    }
    

    效果图如下:

    ASCenterLayoutSpec

    3.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;
    }
    

    效果图如下:

    ASRatioLayoutSpec

    3.7 ASRelativeLayoutSpec

    相对布局有horizontalPositionverticalPosition两个属性,这两个属性都提供了startcenterend这三个位置,所以可以将视图布局在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;
    }
    

    效果图如下:

    ASRelativeLayoutSpec

    3.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;
    }
    

    效果图如下:

    ASAbsoluteLayoutSpec

    3.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的布局规则,可能上手不是那么简单,但是掌握之后,布局起来还是比较方便的。

    相关文章

      网友评论

        本文标题:AsyncDisplayKit学习系列1 - 布局

        本文链接:https://www.haomeiwen.com/subject/xjoavqtx.html