Texture 控件
节点容器(Node Containers)
节点容器 | 等价于 UIKit |
---|---|
ASCollectionNode | 代替 UICollectionView |
ASPagerNode | 代替UIPageViewController |
ASTableNode | 代替UITableView |
ASViewController | 代替UIViewController |
ASNavigationController | 代替UINavigationController,实现 ASVisibility 协议。 |
ASTabBarController | 代替UITabBarController,实现 ASVisibility 协议。 |
为什么使用节点容器,不使用UIViewController
- Node默认是异步布局/渲染,只有在需要将frame/contents等同步到UIView上才会回到主线程,使其空出更多的时间处理其他事件
- Node自动管理其子节点实现智能预加载,这意味着节点的所有布局计算,数据读取,解码和渲染都将会异步完成,这就是为什么我们建议将节点放进节点容器中使用的原因。
节点(Node Subclasses)
节点 | 等价于 UIKit |
---|---|
ASDisplayNode | 代替 UIView,所有的 Node 都继承自 ASDisplayNode。 |
ASCellNode | 代替 UITableViewCell&UICollectionViewCell,需要和 ASTableNode,ASCollectionNode 和 ASPagerNode 共同使用。 |
ASScrollNode | 代替 UIScrollView,这个节点对于创建自定义的,包含其他节点的可滚动区域非常有用。 |
ASEditableTextNode | 代替 UITextView。 |
ASTextNode | 代替 UILabel。 |
ASImageNode | 代替 UIImage。 |
ASNetworkImageNode | 代替 UIImage。 |
ASMultiplexImageNode | 代替 UIImage。 |
ASVideoNode | 代替 AVPlayerLayer。 |
ASVideoPlayerNode | 代替 UIMoviePlayer。 |
ASControlNode | 代替 UIControl。 |
ASButtonNode | 代替 UIButton。 |
ASMapNode | 代替 MKMapView。 |
继承关系
ASDisplayNode
- ASCellNode
- ASTextCellNode
- ASCollectionNode
- ASPagerNode
- ASControlNode
- ASButtonNode
- ASImageNode
- ASMapNode
- ASMultiplexImageNode
- ASNetworkImageNode
- ASVideoNode
- ASTextNode
- ASEditableTextNode
- ASScrollNode
- ASTableNode
- ASVideoPlayerNode
ASDisplayNode
异步绘制步骤
- 在ASDisplayNode.h中有相当多的注释,其中displaysAsynchronously属性大致描述了异步渲染的步骤:
/** Asynchronous rendering proceeds as follows: |
- ASDisplayNode还有一个属性shouldRasterizeDescendants。当我们不需要分别关注单个CALayer,也不需要对他们进行操作时,就可以将所有的子node都合并到父node的backing store一并绘制,从而达到节省内存和提高性能的目的。
/**
* @abstract Whether to draw all descendant nodes’ layers/views into this node’s
> layer/view’s backing store.
* @discussion
* When set to YES, causes all descendant nodes’ layers/views to be drawn
> directly into this node’s layer/view’s backing
* store. Defaults to NO.
* If a node’s descendants are static (never animated or never change attributes
> after creation) then that node is a
* good candidate for rasterization. Rasterizing descendants has two main
> benefits:
* 1) Backing stores for descendant layers are not created. Instead the layers
> are drawn directly into the rasterized
* container. This can save a great deal of memory.
* 2) Since the entire subtree is drawn into one backing store, compositing and
> blending are eliminated in that subtree
* which can help improve animation/scrolling/etc performance.
* Rasterization does not currently support descendants with transform,
> sublayerTransform, or alpha. Those properties
* will be ignored when rasterizing descendants.
* Note: this has nothing to do with -[CALayer shouldRasterize], which doesn’t
> work with ASDisplayNode’s asynchronous
* rendering model.
*/
ASDisplayNode 对象方法
- init 任何线程调用
- 不应该在节点初始化方法中初始化任何 UIKit 对象,以及调用 node.layer node.view.x等与view 或 layer 有关的操作
- touch事件或者手势绑定不要在这里执行
- 这些事件应该在 didLoad 方法中进行。
- didLoad 主线程调用
- 当后台视图初始化完成时,它会被调用一次
- 可以进行任何UI相关操作和初始化
- 需要注意的是,这里是获取不到正确的frame的,获取frame要去layout方法
- layoutSpecThatFits
- 该方法定义了节点的布局,并在后台线程上进行了大量的计算。此方法是你声明、创建和修改 ASLayoutSpec 布局描述对象的地方,该对象描述了节点的 size,以及其子节点的 size 和 position,是你放置大部分布局代码的地方。
- ASLayoutSpec 对象直到在此方法中返回前是可变的。 在这之后,这个对象将不可改变
- 由于它在后台线程上运行,因此你不能在这个方法中调用 node.view 或 node.layer 以及它们的属性。
- 此外,除非你明确知道自己在做什么,否则不要在此方法中创建其他节点
- layout 主线程调用
- 比较类似viewWillLayoutSubviews,在此方法中调用 super 将会使用 layoutSpec 对象计算布局,所有子节点都将计算其 size 和 position。
- 适合进行hidden或者背景色等UI基本信息设置,但不包含布局设置
- 你可以在 -layoutspec: 方法中设定背景颜色,但这可能会存在时序问题。
- 如果要设置UIView相关对象,可以在layout中设置frame,尽量使用initWithViewBlock方法,放到后台进程
- 官方举了个使用例子,colletionNode设置全屏适合在layout方法中调用
一些特有的东西
ASCellNode
- 作用同等于 UITableViewCell 或 UICollectionViewCell,自带 indexPath 属性,不用注册cell
ASButtonNode
@property (nonatomic, assign) CGFloat contentSpacing; // 设置图片和文字的间距
@property (nonatomic, assign) ASButtonNodeImageAlignment imageAlignment;// 图片和文字的排列方式,
/**
ASButtonNodeImageAlignmentBeginning, // 图片在前,文字在后
ASButtonNodeImageAlignmentEnd// 文字在前,图片在后
*/ASNetworkImageNode
- 作用同等于 UIImageView,如果使用网络图片请使用此类,ASDK 用的是第三方的图片加载库PINRemoteImage https://github.com/pinterest/PINRemoteImage,ASNetworkImageNode 其实并不支持 gif,如果需要显示 gif 推荐使用FLAnimatedImage https://github.com/Flipboard/FLAnimatedImage
ASViewController
ASViewController 是一个常规的 UIViewController 子类,它具有管理节点的特殊功能。因为它是一个 UIViewController 子类,所以所有的方法都在主线程上被调用,并且你应该在主线程上创建至少一个 ASViewController。
ASViewController 对象方法
- init
- 这个方法在 ASViewController 的生命周期开始时被调用一次
- 不要访问self.view或者self.node.view,这样会强迫视图被过早初始化,以免引发问题。
- 自带initWithNode方法初始化,其自身管理node的方法和view类似
- loadView
- 官方不建议使用这个方法
- viewDidLoad
- 跟 UIViewController一样,这个方法在 -loadView 之后被执行
- 可以访问 node.view 最早的方法,你可以在这份方法中任意修改 view 和 layer 或添加手势,这个方法在其所属的生命周期中,只会执行一次。
- 布局代码不应该放在这个方法中,因为当界面重绘时,这里的代码不会被再次调用。UIViewController 中这个方法也是同样的,在这种方法中放置布局代码是一种不太好的做法,即使你的布局不会因为交互发生变化。
- viewWillLayoutSubviews
- 这个方法会与Node的 -layout 同时调用,它可能在 ASViewController 的生命周期中被多次调用
- 当 ASViewController 的节点的边界发生改变,如旋转、分割屏幕、键盘弹出等行为,或者当视图的层次结构发生变化,如子节点添加、删除或改变大小时,这个方法将被调用。
- 官方建议把布局逻辑写在这里(不强依赖于size的代码)
- viewWillAppear / viewDidDisappear
- 适合统计用户行为log
- 适合进行Controller的动画操作
Texture 布局
ASLayout
@interface ASLayout : NSObject |
可以看出,当一个node具备了确定的ASLayout对象时,它自身的布局也就随之确定了
只要Node能够计算出自己的ASLayout,父元素就可以完成对其的布局。这种方法(measureWithSizeRange:)将sizeThatFits和layoutSubviews结合在一起,一定程度上避免了相似代码的尴尬,但是计算上仍然是手动布局,不够简便。
ASLayoutSpec
- 存储着ASDK可以“理解”的布局信息,供布局系统进行布局,这个类一般不会直接接触到(除非你需要自己实现一个LayoutSpec),基本可以理解为,这个类存储了以下三个重要信息:layout信息所作用的对象(layoutableObject)、layout的位置(position)、layout的大小(size)。
- ASLayout类存储着布局的信息,但若所有布局都要靠我们自己创建ASLayout对象,那几乎也就和纯手算然后setFrame没有太大区别了,ASLayoutSpec及其子类可以看做是一个上层接口,让开发者不必考虑复杂的构建ASLayout对象的过程,而通过这些LayoutSpec类来构造自己的布局,这个类及其子类将会是我们在开发的时候见到最多的。
- ASLayoutSpec只负责指定布局规则,而不关心其布局的具体是Node还是其他ASLayoutSpec
- ASLayoutSpec 的作用更像是一个抽象类,在真正使用 ASDK 的布局引擎时,都不会直接使用这个类,而是会用类似 ASStackLayoutSpec、ASRelativeLayoutSpec、ASOverlayLayoutSpec 以及 ASRatioLayoutSpec 等子类。
Layout Types
Layout继承关系
ASLayoutSpec 与下面的所有的 Spec 类都是继承关系,在视图需要布局时,会调用 ASLayoutSpec 或者它的子类的 - measureWithSizeRange: 方法返回一个用于布局的对象 ASLayout。
几种布局规则
规则 | 描述 |
---|---|
ASWrapperLayoutSpec | 填充布局 |
ASStackLayoutSpec | 盒子布局 |
ASInsetLayoutSpec | 插入布局 |
ASOverlayLayoutSpec | 覆盖布局 |
ASBackgroundLayoutSpec | 背景布局 |
ASCenterLayoutSpec | 中心布局 |
ASRatioLayoutSpec | 比例布局 |
ASRelativeLayoutSpec | 顶点布局 |
ASAbsoluteLayoutSpec | 绝对布局 |
以上几种布局就不一一介绍了,具体可以看官方的DEMO
Layout Element 布局元素属性
- ASStackLayoutElement Properties:只会在盒子布局中的的
subnode
或layoutSpec
中生效; - ASAbsoluteLayoutElement Properties:只会在绝对布局中的的
subnode
或layoutSpec
中生效; - ASLayoutElement Properties:适用于所有
Node
和layoutSpec
;
ASStackLayoutElement Properties
请注意,以下属性只有在 ASStackLayout
的 subnode
上设置才会生效。
.style.spacingBefore
CGFloat
类型,direction 上与前一个 node 的间隔。
.style.spacingAfter
CGFloat
类型,direction 上与后一个 node 的间隔。
.style.flexGrow
Bool
类型,子节点尺寸总和小于 minimum ,即存在剩余空间时,是否放大。
.style.flexShrink
Bool
类型,子节点总和大于 maximum,即空间不足时,是否缩小。
.style.flexBasis
ASDimension
类型,描述在剩余空间是均分的情况下,应用 flexGrow
或 flexShrink
属性之前,该对象在盒子中垂直或水平方向的初始 size
,
.style.alignSelf
ASStackLayoutAlignSelf
类型,描述对象在十字轴的方向,此属性会覆盖 alignItems
,可选值有:
ASStackLayoutAlignSelfAuto
ASStackLayoutAlignSelfStart
ASStackLayoutAlignSelfEnd
ASStackLayoutAlignSelfCenter
ASStackLayoutAlignSelfStretch
.style.ascender
CGFloat
类型,用于基线对齐,描述对象从顶部到其基线的距离。
.style.descender
CGFloat
类型,用于基线对齐,描述对象从基线到其底部的距离。
ASAbsoluteLayoutElement Properties
请注意,以下属性只有在 AbsoluteLayout
的 subnode
上设置才会生效。
.style.layoutPosition
CGPoint
类型,描述该对象在 ASAbsoluteLayoutSpec
父规则中的位置。
ASLayoutElement Properties
请注意,以下属性适用于所有布局元素。
.style.width
ASDimension
类型,width
属性描述了 ASLayoutElement
内容区域的宽度。 minWidth
和 maxWidth
属性会覆盖 width
, 默认值为 ASDimensionAuto
。
.style.height
ASDimension
类型,height
属性描述了 ASLayoutElement
内容区域的高度。 minHeight
和 maxHeight
属性会覆盖 height
,默认值为 ASDimensionAuto
。
.style.minWidth
ASDimension
类型,minWidth
属性用于设置一个特定布局元素的最小宽度。 它可以防止 width
属性的使用值小于 minWidth
指定的值,minWidth
的值会覆盖 maxWidth
和 width
。 默认值为 ASDimensionAuto
。
.style.maxWidth
ASDimension
类型,maxWidth
属性用于设置一个特定布局元素的最大宽度。 它可以防止 width
属性的使用值大于 maxWidth
指定的值,maxWidth
的值会覆盖 width
,minWidth
会覆盖 maxWidth
。 默认值为 ASDimensionAuto
。
.style.minHeight
ASDimension
类型,minHeight
属性用于设置一个特定布局元素的最小高度。 它可以防止 height
属性的使用值小于 minHeight
指定的值。 minHeight
的值会覆盖 maxHeight
和 height
。 默认值为 ASDimensionAuto
。
.style.maxHeight
ASDimension
类型,maxHeight
属性用于设置一个特定布局元素的最大高度,它可以防止 height
属性的使用值大于 maxHeight
指定的值。 maxHeight
的值会覆盖 height
,minHeight
会覆盖 maxHeight
。 默认值为 ASDimensionAuto
。
.style.preferredSize
CGSize
类型, 建议布局元素的 size
应该是多少。 如果提供了 minSize
或 maxSize
,并且 preferredSize
超过了这些值,则强制使用 minSize
或 maxSize
。 如果未提供 preferredSize
,则布局元素的 size
默认为 calculateSizeThatFits:
方法提供的固有大小。
此方法是可选的,但是对于没有固有大小或需要用与固有大小不同的的 size 进行布局的节点,则必须指定 preferredSize
或 preferredLayoutSize
中的一个,比如没这个属性可以在 ASImageNode
上设置,使这个节点的 size
和图片 size
不同。
警告:当 size 的宽度或高度是相对值时调用 getter 将进行断言。
.style.minSize
CGSize
类型,可选属性,为布局元素提供最小尺寸,如果提供,minSize
将会强制使用。 如果父级布局元素的 minSize
小于其子级的 minSize
,则强制使用子级的 minSize
,并且其大小将扩展到布局规则之外。
例如,如果给全屏容器中的某个元素设置 50% 的 preferredSize
相对宽度,和 200pt 的 minSize
宽度,preferredSize
会在 iPhone 屏幕上产生 160pt 的宽度,但由于 160pt 低于 200pt 的 minSize
宽度,因此最终该元素的宽度会是 200pt。
.style.maxSize
CGSize
类型,可选属性,为布局元素提供最大尺寸,如果提供,maxSize
将会强制使用。 如果子布局元素的 maxSize
小于其父级的 maxSize
,则强制使用子级的 maxSize
,并且其大小将扩展到布局规则之外。
例如,如果给全屏容器中的某个元素设置 50% 的 preferredSize
相对宽度,和 120pt 的 maxSize
宽度,preferredSize
会在 iPhone 屏幕上产生 160pt 的宽度,但由于 160pt 高于 120pt 的 maxSize
宽度,因此最终该元素的宽度会是 120pt。
.style.preferredLayoutSize
ASLayoutSize
类型,为布局元素提供建议的相对 size
。 ASLayoutSize
使用百分比而不是点来指定布局。 例如,子布局元素的宽度应该是父宽度的 50%。 如果提供了可选的 minLayoutSize
或 maxLayoutSize
,并且 preferredLayoutSize
超过了这些值,则将使用 minLayoutSize
或 maxLayoutSize
。 如果未提供此可选值,则布局元素的 size
将默认是 calculateSizeThatFits:
提供的固有大小。
.style.minLayoutSize
ASLayoutSize
类型, 可选属性,为布局元素提供最小的相对尺寸, 如果提供,minLayoutSize
将会强制使用。 如果父级布局元素的 minLayoutSize
小于其子级的 minLayoutSize
,则会强制使用子级的 minLayoutSize
,并且其大小将扩展到布局规则之外。
.style.maxLayoutSize
ASLayoutSize
类型, 可选属性,为布局元素提供最大的相对尺寸。 如果提供,maxLayoutSize
将会强制使用。 如果父级布局元素的 maxLayoutSize
小于其子级的 maxLayoutSize
,那么将强制使用子级的 maxLayoutSize
,并且其大小将扩展到布局规则之外。
一些布局小贴士
- 更新布局,记得调用setNeedsLayout(经过本人测试,这个方法,同时会刷新相关的所有的Node)
- automaticallyManagesSubnodes 方法可以自动管理子Node,不用手动addSubNode(需要注意的是,要实现对应的布局才会添加到父视图里)
- inverted 可以翻转整个tableNode、CollectionNode的NSIndexPath,最下方的cellNode变成NSIndexPath(0, 0)
- imageModificationBlock 是ImageNode的block,可以做一个后期处理,比如圆角裁剪等
- shouldRasterizeDescendants 子树光栅化,意味着类似VVebo的效果,一个点内的所有node层级都会绘制到一个layer上
- neverShowPlaceholders 同步绘制cellNode,主线程会锁死,直到界面完成绘制
- hitTestSlop属性可以扩大点击响应区域(但是同时,也会影响frame)
- enableHitTestDebug ADK提供很多debug功能,这个是测试相应区域的,可响应区域会变成绿色
一些官方的建议
- 常见错误
- 禁止在-init:方法访问node.view
- 确保在node block外部访问data source
- viewBlocks避免循环引用
- 常见概念误区
- ASCellNode不会复用
- LayoutSpecs会在每次layout被调用时执行
- AsyncDisplayKit有自己的强大布局API,用法上和系统的API又很大区别
- 常见性能问题场景
- 避免使用cornerRadius属性(以及shadowPath,border, mask),CALayer的cornerRadius是一个性能杀手,他是CALayer上效率最低,渲染密度最高的属性之一,这些属性会出发离屏渲染,也就是说滚动的同时实时渲染,具体可以查看官方的圆角指南
- ASDK不支持Auto Layout
- ASDisplayNode的指针会保持alive(据说系统中当layer被添加到UIView的super layer上后,也会一直保留,即使layer的delegate被置为weak)
- Corner Rounding 关于圆角性能
- 官网有一篇圆角的分析和优化文章,具体可以看文档