iOS Facebook Texture

221 阅读11分钟

AsyncDisplayKit是facebook发布的UI框架,基本单位为node,能有效的提高界面的流畅性

目前已经改名为Texture ,需要进行对应的升级

pod 'AsyncDisplayKit'更改为

pod 'Texture', '~> 2.3.4'

Texture 包含了整个'AsyncDisplayKit' ,基本方法都不变,只需引入即可

github地址:github.com/texturegrou…

第三方库 WebASDKImageManager 有对图片缓存的category ,但只支持AsyncDisplayKit,没有支持最新版的Texture

Texture 在podfile 里 platform 最低支持iOS8.0

官方Texture文档:texturegroup.org/docs/gettin…

如果您刚开始使用Texture,我们建议您先从[ASDKgram(github.com/texturegrou…)
示例应用程序开始,该应用程序将使用UIKit实现的照片Feed与使用Texture实现的相同Feed进行比较。

UIKit的绘制机制图解

CALayer的display方法由系统调用,用来更新layer的内容,如果layer有delegate对象,那么display方法将尝试调用delegate的displayer:方法来更新layer的内容。如果delegate没有实现displaylayer:方法,则这个方法会创建一个backing store来保存原来的内容,然后调用layer的drawInContext:方法来填充back store。最后以新的back store替换layer之前内容达到更新layer的目的。通常UIKit中CAlayer的delegate是UIView对象

两种方式自定义CAlayer的内容

image.png

有时一个 layer会包含很多 sub-layer,而这些sub-layer并不需要响应触摸事件,也不需要进行动画和位置调整。ASDK为此实现了一个被称为pre-composing的技术,可以把这些sub-layer合成渲染为一张图片。开发时,ASNode已经替代了UIView和 CALayer;直接使用各种Node控件并设置为layer backed后,ASNode甚至可以通过预合成来避免创建内部的UIView和CALayer。 通过这种方式,把一个大的层级,通过一个大的绘制方法绘制到一张图上,性能会获得很大提升。CPU避免了创建UIKit对象的资源消耗,GPU避免了多张texture合成和渲染的消耗,更少的bitmap也意味着更少的内存占用。

Node异步绘制

image.png

RunLoop机制

iOS一个runloop循环为1/60秒

ASNode把任务用ASAsyncTransaction(Group)封装并提交到一个全局的容器,并注册一个比coreAnimation优先级低的Observe,coreAnimation执行任务后再执行Node的任务的内容,并在合适的机会异步并发同步到主线程

Texture允许您将图像解码、文本大小和渲染以及其他昂贵的UI操作从主线程上移动,以保持主线程响应用户交互。ASLayoutSpec为每个节点提供一个来执行异步测量和布局

texture使用Flex布局(不是很懂,布局格式可以参考bawn.github.io/2017/12/Tex…

一、Layout 布局

1、LayoutSpecs(布局规则)

LayoutSpecs是“layout specification”的缩写,没有物理存在。
相反,LayoutSpecs充当其他LayoutElements的容器,来理解这些子LayoutElements如何相互关联。
AsyncDisplayKit提供了ASLayoutSpec的几个子类。
从插入单个简单布局规则到更多更复杂的布局规则,变化堆放排列配置。

2、LayoutElements(布局元素)

LayoutSpecs包含并排列LayoutElements
所有ASDisplayNodes和ASLayoutSpecs都符合<ASLayoutElement>协议
这意味着您可以从Nodes和其他LayoutSpecs构成LayoutSpecs
ASLayoutElement协议有几个属性,可用于创建非常复杂的LayoutSpecs
此外LayoutSpecs也具有自己的一组属性,可用于调整LayoutElements的排列

3、组合LayoutSpecs和LayoutElements,创建复杂的UI

可以看到如何将ASTextNode(黄色高亮),ASVideoNode(顶部图像)和ASStackLayoutSpec(“堆放布局规则”)组合来创建复杂布局。

image.png

使用ASCenterLayoutSpec(“中心布局规则”)和ASOverlayLayoutSpec(“覆盖布局规则”),来放置顶部ASVideoNode(顶部图像)的播放按钮。

image.png

4、一些Node需要固定大小

#一些元素具有一个”固有大小“,基于他们可用内容。
例如,ASTextNode可以根据其属性字符串计算其大小,其他具有固有大小的Node包括:
ASImageNode
ASTextNode
ASButtonNode
ASTextNode
#所有其他Node在外部资源加载完成之前没有或者缺乏固有大小。
例如,在从URL下载图像之前,ASNetworkImageNode不知道它的大小。这些种类包括:
ASVideoNode
ASVideoPlayerNode
ASNetworkImageNode
ASEditableTextNode
#注意:
#缺少初始固有大小的这些Node必须设置它们的初始大小,使用ASRatioLayoutSpec(“比例布局规则”),ASAbsoluteLayoutSpec(“绝对布局规则”)或者对象的size属性。

5、Layout调试

#在任何ASDisplayNode或ASLayoutSpec上调用-asciiArtString,会返回对象及其子对象的字符图。
(可选)如果在任何Node或layoutSpec上设置.debugName,那么也将包含在字符图。
例如:
  -----------------------ASStackLayoutSpec----------------------
|  -----ASStackLayoutSpec-----  -----ASStackLayoutSpec-----  |
|  |       ASImageNode       |  |       ASImageNode       |  |
|  |       ASImageNode       |  |       ASImageNode       |  |
|  ---------------------------  ---------------------------  |
--------------------------------------------------------------
#可以在任何ASLayoutElement(node或layoutSpec)上打印对象样式,调整大小属性时极其方便。
例如:
(lldb) po _photoImageNode.style
Layout Size = min {414pt, 414pt} <= preferred {20%, 50%} <= max {414pt, 414pt}

二、Layout Examples(布局示例)

1、Simple Header with Left and Right Justified Text(简单标题左右对齐)

image.png

示例工程

约束说明
ASStackLayoutSpec垂直的
ASStackLayoutSpec水平的
ASInsetLayoutSpec插入整个标题

布局的组成(layout specs + nodes),如图:

image.png

//Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
  
  // 当用户名和位置信息文本太长时,收缩堆放视图来适应屏幕,而不是将所有内容向右堆放
  ASStackLayoutSpec *nameLocationStack = [ASStackLayoutSpec verticalStackLayoutSpec];
  nameLocationStack.style.flexShrink = 1.0;
  nameLocationStack.style.flexGrow = 1.0;
  
  //如果从服务器获取位置信息,并检查位置信息是否可用
  if (_postLocationNode.attributedText) {
    nameLocationStack.children = @[_usernameNode, _postLocationNode];
  } else {
    nameLocationStack.children = @[_usernameNode];
  }
  
  //水平堆放
  ASStackLayoutSpec *headerStackSpec = [ASStackLayoutSpec   stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
                     spacing:40     
              justifyContent:ASStackLayoutJustifyContentStart
                  alignItems:ASStackLayoutAlignItemsCenter
                    children:@[nameLocationStack, _postTimeNode]];
  
  //插入堆放
  return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 10, 0, 10)    
                                                child:headerStackSpec];
  
}

将示例项目从纵向旋转到横向,看看间隔体是如何增长和收缩的。

2、Photo with Inset Text Overlay(图片上覆盖文本)

image.png

约束说明
ASInsetLayoutSpec插入文本
ASOverlayLayoutSpec插入文本覆盖在图片上
//Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
  
  _photoNode.style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT*2, USER_IMAGE_HEIGHT*2);
  
  // INIFINITY(插入无边界)
  UIEdgeInsets insets = UIEdgeInsetsMake(INFINITY, 12, 12, 12);
  ASInsetLayoutSpec *textInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_titleNode];
  
  return [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_photoNode 
                                                 overlay:textInsetSpec];
  
}

3、Photo with Outset Icon Overlay(图片上覆盖和图标)

image.png

约束说明
ASAbsoluteLayoutSpec放置照片和icon
ASLayoutable属性单独调整大小和位置
//Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
  
  _iconNode.style.preferredSize = CGSizeMake(40, 40);
  _iconNode.style.layoutPosition = CGPointMake(150, 0);
  
  _photoNode.style.preferredSize = CGSizeMake(150, 150);
  _photoNode.style.layoutPosition = CGPointMake(40 / 2.0, 40 / 2.0);
  
  return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithSizing:ASAbsoluteLayoutSpecSizingSizeToFit
                                                   children:@[_photoNode, _iconNode]];
  
}  

4、Simple Inset Text Cell(简单插入文本单元格)

image.png

约束说明
ASInsetLayoutSpec插入文本
ASCenterLayoutSpec根据指定属性文本居中
//Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{

    UIEdgeInsets insets = UIEdgeInsetsMake(0, 12, 4, 4);
    ASInsetLayoutSpec *inset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets
                                                                      child:_titleNode];

    return [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringY                                                  
    sizingOptions:ASCenterLayoutSpecSizingOptionMinimumX 
            child:inset];
            
}

5、Top and Bottom Separator Lines(顶部和底部分隔线)

约束说明
ASInsetLayoutSpec插入文本
ASStackLayoutSpec垂直的堆放文本上下分割线

布局的组成(layout specs + nodes),如图:

image.png

//Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{

  _topSeparator.style.flexGrow = 1.0;
  _bottomSeparator.style.flexGrow = 1.0;

  ASInsetLayoutSpec *insetContentSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(20, 20, 20, 20) child:_textNode];

  return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
                                                 spacing:0
                                          justifyContent:ASStackLayoutJustifyContentCenter
                                              alignItems:ASStackLayoutAlignItemsStretch
                                                children:@[_topSeparator, insetContentSpec, _bottomSeparator]];
}
  

三、Layout Specs(布局规则)

以下ASLayoutSpec子类,用于组成简单或复杂的布局

布局规则说明
ASInsetLayoutSpec插入布局
ASOverlayLayoutSpec覆盖布局
ASBackgroundLayoutSpec背景布局
ASCenterLayoutSpec中心布局
ASRatioLayoutSpec比例布局
ASStackLayoutSpec堆叠布局
ASAbsoluteLayoutSpec绝对布局

你可以子类化ASLayoutSpec,自定义ASLayoutSpec

1、ASInsetLayoutSpec(插入布局规则)

在布局过程中,ASInsetLayoutSpec通过constrainedSize.max传递插入减掉后的CGSize给子项,一旦子项确定它的最终尺寸,插入规则将其最终尺寸加上其插入边距向上传递,由于插图布局规则的大小基于其子项的大小,所以子项必须具有固有大小或明确设置其大小。

image.png

如果在UIEdgeInsets中设置了INFINITY作为值,插入规则只使用子项固有大小。

//Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
  ...
  UIEdgeInsets *insets = UIEdgeInsetsMake(10, 10, 10, 10);
  ASInsetLayoutSpec *headerWithInset = insetLayoutSpecWithInsets:insets child:textNode];
  ...
}

2、ASOverlayLayoutSpec(覆盖布局规则)

ASOverlayLayoutSpec布局一个组件(红色),作为覆盖伸展到另个组件(蓝色)之前覆盖布局的大小,是根据子项的大小计算得出的。下图中,子项是蓝色层,然后子项的大小作为constrainedSize传递给覆盖布局元素(红色层),子项(蓝色层)必须具有固有大小或在其上设置的大小。

image.png

//Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
  ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
  ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]
  return [ASOverlayLayoutSpec overlayLayoutSpecWithChild:backgroundNode overlay:foregroundNode]];
}

3、ASBackgroundLayoutSpec(背景布局规则)

ASBackgroundLayoutSpec布局一个组件(红色),作为背景伸展到另一个组件(蓝色)之后背景布局的大小,是根据子项的大小计算得出的。下图中,子项是蓝色层,然后,子项的大小作为constrainedSize传递给背景布局元素(红色层),子项(蓝色层)必须具有固有大小或在其上设置的大小。

image.png

//Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
  ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
  ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
  return [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:foregroundNode background:backgroundNode]];
}

4、ASCenterLayoutSpec(中心布局规则)

ASCenterLayoutSpec将其子项居中在其最大值中constrainedSize。
如果中心规格的宽度或高度不受约束,它会缩小到子项的大小。

ASCenterLayoutSpec的两个属性:

属性说明
centeringOptions确定中心位置None,X,Y,XY
sizingOptions确定中心占用空间Default,minimum X,minimum Y,minimum XY

image.png

//Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
  ASStaticSizeDisplayNode *subnode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(70, 100));
  return [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY                                             sizingOptions:ASRelativeLayoutSpecSizingOptionDefault                                                     child:subnode]
}

5、ASRatioLayoutSpec(比例布局规则)

ASRatioLayoutSpec布局缩放固定宽高比,此规则必须具有作为constrainedSize传递给它的宽度或高度,因为它使用此值来缩放自身。
使用比例布局为ASNetworkImageNode或ASVideoNode提供固有大小是非常常见的,因为两者在服务器返回内容之前都没有内在大小。

image.png

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
  //一半比例
  ASStaticSizeDisplayNode *subnode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(100, 100));
  return [ASRatioLayoutSpec ratioLayoutSpecWithRatio:0.5 child:subnode];
}

6、ASRelativeLayoutSpec(相对布局规则)

根据垂直和水平位置说明范围内布局组件,子项可以被定位在4个角中的任何一个,或者4个边缘中的任何一个,以及中心。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
  ...
  ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
  ASStaticSizeDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(70, 100));

  ASRelativeLayoutSpec *relativeSpec = [ASRelativeLayoutSpec relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
                                verticalPosition:ASRelativeLayoutSpecPositionStart
                                    sizingOption:ASRelativeLayoutSpecSizingOptionDefault                                                                       
                                           child:foregroundNode]

  ASBackgroundLayoutSpec *backgroundSpec = [ASBackgroundLayoutSpecbackgroundLayoutSpecWithChild:relativeSpec background:backgroundNode];
  ...
}

7、ASStackLayoutSpec(堆叠布局规则)

在ASDK中的所有layoutSpecs中,ASStackLayoutSpec是非常强大的,ASStackLayoutSpec使用flexbox算法来确定其子节点的位置和大小,Flexbox旨在在不同的屏幕尺寸上提供一致的布局,在堆叠布局中,以垂直或水平堆叠对齐item。堆叠布局可以是另一个堆叠布局的子布局,这使得可以使用堆叠布局规则创建几乎任何布局。

ASStackLayoutSpec除了ASLayoutElement还有7个属性:

属性说明描述
direction方向指定堆叠方向,如果设置了horizontalAlignment和verticalAlignment,它们将被再次解决,导致justifyContent和alignItems被相应地更新。
spacing间距每个子元素之间的距离。
horizontalAlignment水平对齐指定子元素如何水平排列,取决于堆叠方向,设置对齐会导致justifyContent或alignItems被更新。未来方向更改后,对齐将保持有效。因此,优选那些性质。
verticalAlignment竖直对齐指定子元素如何垂直排列,取决于堆叠方向,设置对齐会导致justifyContent或alignItems被更新。未来方向更改后,对齐将保持有效。因此,优选那些性质。
justifyContent对齐内容每个子元素之间的距离。
alignItems对齐Item子元素沿着横轴的方向。
baselineRelativeArrangement基线相对布局如果YES,则从顶视图的最后基线到底视图的顶部测量两个视图之间的垂直间距。
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{

  ASStackLayoutSpec *mainStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal                                                                                   spacing:6.0
        justifyContent:ASStackLayoutJustifyContentStart
     alignItems:ASStackLayoutAlignItemsCenter
                  children:@[_iconNode, _countNode]];

  //设置一些大小约束
  mainStack.minWidth = ASDimensionMakeWithPoints(60.0);
  mainStack.maxHeight = ASDimensionMakeWithPoints(40.0);

  return mainStack;
}

说明:

Flexbox在AsyncDisplayKit中的工作方式与在Web上的CSS中的工作方式相同,有一些例外。 默认值不同,没有flex参数,flexGrow和flexShrink只支持一个布尔值。

8、ASAbsoluteLayoutSpec(绝对布局约束)

在ASAbsoluteLayoutSpec中,可以通过设置其layoutPosition属性来指定其子元素的确切位置(x / y坐标),绝对布局比其他类型的布局更不灵活和难以维护。

ASAbsoluteLayoutSpec属性:

属性说明
sizing大小Default / Size to Fit

确定绝对规格将占用多少空间。
注意:Size to Fit选项将复制旧的ASStaticLayoutSpec行为。

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

  CGSize maxConstrainedSize = constrainedSize.max;

  //在静态布局中布局所有Node
  guitarVideoNode.layoutPosition = CGPointMake(0, 0);
  guitarVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width, maxConstrainedSize.height / 3.0));

  nicCageVideoNode.layoutPosition = CGPointMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0);
  nicCageVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0));

  simonVideoNode.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height - (maxConstrainedSize.height / 3.0));
  simonVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width/2, maxConstrainedSize.height / 3.0));

  hlsVideoNode.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height / 3.0);
  hlsVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0));

  return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[guitarVideoNode, nicCageVideoNode, simonVideoNode, hlsVideoNode]];
}

9、ASLayoutSpec(布局规则)

ASLayoutSpec是所有布局规则都被子类化的父类,它的主要工作是处理和管理所有的子类,但它也可以用于创建自定义布局规格,只有超级高级应该希望/需要创建ASLayoutSpec的自定义子类。相反,尝试使用提供的布局规则,并将它们组合在一起以创建更高级的布局。

ASLayoutSpec的另一个用途是充当ASStackLayoutSpec中的其他子元素,在使用.flexGrow和/或.flexShrink时。

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
  ...
  // ASLayoutSpec作为间隔
  let spacer = ASLayoutSpec()
  spacer.flexGrow = true

  stack.children = [imageNode, spacer, textNode]
  ...
}

10、Layout Element Properties(布局元素属性)

属性说明
ASStackLayoutElement Properties只对stack堆叠布局的Node生效
ASAbsoluteLayoutElement Properties只对absolute绝对布局的Node生效
ASLayoutElement Properties对所有布局和Node生效

Demo

参考:www.jianshu.com/p/afc69cd9e…