美文网首页ASDK技术栈iOS技术专题
ASDK的使用(二): 自动布局

ASDK的使用(二): 自动布局

作者: iYeso | 来源:发表于2019-01-14 18:15 被阅读52次

AsyncDisplayKit2.0 教程的第二部分中,了解在iOS应用程序中构建快速灵活的布局是多么容易。

image
AsyncDisplayKit 的布局系统让你以声明式语法编写布局代码,快到令人难以置信。你可以编写一个能够在view controller 或者iPad apppopover 中使用的节点。如果正确编写它的布局的话,你可以将这个节点迁移到新的环境中,而无需修改布局代码!

在本教程中,你将使用第一课中用过的CardNode 类,并利用它来学习布局约束。你会看到要实现它的布局约束是多么的简单,这正是你所期望的。

一: 自动布局的问题

你也许会问“用自动布局不行吗?”。使用自动布局,每个约束都会以表达式的形式放在表达式系统中。也就是说,每个约束都让约束解析器的执行时间以指数级增长。这种计算总是在主线程中进行的。
ASDK 的设计目标之一,就是尽可能地接近 UIKitAPI。不幸的是,自动布局是一个封闭的系统,无法告诉布局解析器在另一个线程中完成这个工作。

二: 开始

要开始,请在此处下载入门项目。因为接下来将学习的内容和布局规范相关,我们需要将本系列第一课中的完成项目的版本进行修改。

注意:在完成AsyncDisplayKit 2.0教程之前,请务必查看第1部分,了解Async Display Kit的介绍

三: ASLayoutSpec 介绍

首先,科普一点历史。

布局规范在构建 Paper 大事记中提到的一个布局系统。目的是让节点及其子节点的位置、大小的计算和应用规范化和可重用。

在 ASDK 1.9.x 中,你可以创建异步的布局,但布局代码和 UIKit 中预布局的方式差不多。节点的子节点的大小在一个 calculateSizeThatFits: 方法中计算。这个 size 可被缓存并在随后的 -layout 方法中应用。节点的位置仍然需要进行老旧的数学方法计算——没有人喜欢和数学计算打交道。
呃,好吧,大部分人都不喜欢和数学打交道!

image

好的,很好,大多数人不喜欢搞乱数学!

四: Layout Specs

使用ASDK 2.0,ASDisplayNode子类会实现 -layoutSpecThatFits 方法。 ASLayoutSpec对象确定所有子节点的大小和位置。在这样做时,布局规范还确定所述父节点的大小.

一个节点会返回一个layout spec 对象——通过 -layoutSpecThatFits:方法。这个对象会结算节点的大小,并循环计算它的所有子节点的大小和位置。

ThatFits 方法参数是一个ASSizeRange。它有两个CGSize 属性,一个最小size,一个最大size。分别定义该节点的最小尺寸和最大尺寸。

ASDK 提供了很多种layout spec。它们是:

  • 1:ASStackLayoutSpec: 允许你定义一个水平或垂直的子节点栈。它的justifyContent 属性决定栈在相应方向上的子节点之间的间距。alignItems属性决定了它们在另一个坐标轴上的间距。这种layout specs 有点像UIKitUIStackView
  • 2:ASOverlayLayoutSpec: 允许你拉伸一个元素横跨到另一个元素。被覆盖的对象必须要有一个固定的content size,否则无法工作
  • 3:ASRelativeLayoutSpec: 一种相对布局,允许将一个东西以相对位置放置在它的有效空间内。参考一下“9-切片图”的 9 个切片。你可以让一个东西放在这 9 个切片中的某个上。.
  • 4: ASInsetLayoutSpec: 一个 inset 布局,允许你在一个已有的对象的基础上添加某些间距。你想在你的 cell 四周加上经典的 iOS 16 像素的边距吗?用这个就对了。

五: ASLayoutElement协议

Layout specs负责管理一个或多个子节点。一个layout spec 的子节点应该是一个节点比如ASTextNodeASImageNode。或者除了节点之外,layou spec的子节点也可以是另一个layout spec

噢,太不可思议了!
Layout sepc 必须实现ASLayoutElement 协议。ASLayoutSpecASDisplayNode 都实现了ASLayoutElement。因此这两者及其子类都可以作为layout spec 的子节点。

1.png

这个简单的概念带来的功能无比强大。最重要的一种layout specASStackLayoutSpec。它能够将一张图片和一个文本装在一起,也能够将一张图片和另一个 stack 装在一起。

你猜对了。该实战检验一下了!我指的是敲代码…

4.png

六: 添加动物图片

假设在你上班的时候,设计师发来一张图片,她希望新的动物百科 app 应当长成这个样子:

动物截图.png

为了表达整个布局,首先需要将它分解成几个对应的layout spec。有时候可能会觉得无所适从,但别忘了,layout spec 的威力取决于它们能够组合起来有多容易。开始越简单越好。

稍稍的剧透一下,你可以用一个 stack 将上半部分和下半部分组合起来就是了。现在你明白了吧,你可以分别对两个分开的部分单独布局,然后将它们组合在一起。

解压缩启动项目并打开RainforestStarter.xcworkspace。找到CardNode.mlayoutSpecThatFits 方法。现在它只是简单地返回一个空的ASLayoutSpec 对象。

如果你运行 app,你会看到:


3.png

呃,这只是开头。首先来一张动物图片怎样?

默认,一个网络图片节点没有内容,当然也没有固定尺寸(intrinsic size)。当然,从截屏图中你可以算出动物图片大概占据整屏宽度和 2/3 的屏幕高度。

要实现这个,将 return 语句替换为:

//1
CGFloat ratio = constrainedSize.min.height/constrainedSize.min.width;

//2
ASRatioLayoutSpec *imageRatioSpec = [ASRatioLayoutSpec 
                                            ratioLayoutSpecWithRatio:ratio 
                                                               child:self.animalImageNode];
//3
return imageRatioSpec;

依次记录每个编号的评论:

  • 1: 计算比例:首先以高/宽比的形式定义图片所用的比例。这里,你指定图片的高度为 cell 最小高度即屏幕高度的 2/3。

  • 2: 创建一个ratio layout spec然后,创建一个ASRatioLayoutSpec,使用我们算好的比例以及一个子节点animalImageNode 作为参数。

  • 3: 返回 spec:返回imageRatioSpec,用于指定 cell 的宽高。

运行 app,看看你的 layout spec 是什么样子:

呃,这么简单?因为只有图片拥有 size,cell 会自动被它撑大。

image.png

注意:传递给cellconstrainedSize 由一个最小值(0,0) 和一个最大值(tableNodeWidth,INF) 构成,这是为什么需要用perfrerredFrameSize 作为图片高度的原因。preferredFrameSize在第一课的 AnimalPageController 中进行赋值。

七: 添加渐变

现在您已拥有动物图像,下一个逻辑步骤是在其上添加渐变节点。 ASOverlayLayoutSpec只是作业的规范。

首先,在imageRatioSpec初始化后添加以下行:

ASOverlayLayoutSpec *gradientOverlaySpec = [ASOverlayLayoutSpec
                                              overlayLayoutSpecWithChild:imageRatioSpec 
                                                                 overlay:self.gradientNode];

在创建自己的layout spec时,总是用一个spec 将其他spec 包含起来。目前,这个 spec 就是gradientOverlaySpec
将 return 语句替换为:

return gradientOverlaySpec;

运行 app,可以看到每个 imageNode 上面都会覆盖以一个渐变色。


image.png

每只鸟都覆盖着一层渐变色——很漂亮!

八: 加入动物名称

上半部唯一要做的就是显示动物的名字。
要完成上半部分,还剩下一件事情就是:显示动物名称。

这好像不是太难,只需要注意几个地方:

  • 1: 名字应当放在渐变层之上。
  • 2:名字应当位于动物图片的左下角
  • 3: 左边留白 16 个像素,底部留白 8 个像素

你已经知道如何将文字放在哪个布局上面了。现在让我们付诸实践。

gradientOverlaySpec之后添加以下行。

ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec
                                          overlayLayoutSpecWithChild:gradientOverlaySpec 
                                                             overlay:self.animalNameTextNode];

然后,将 return 语句改成:

return nameOverlaySpec;

运行程序,你能看到文字了:


image.png

不错,现在需要把它挪到左下角。

这时需要解释一下了。在鸟的图片上有一些文字,因此你很自然地会将 nameOverlaySpec 添加到另一个 spec 中并放到指定的位置。这时你需要后头想想,你到底想要什么。

这里,你用 nameOverlaySpec 去包含某个对象,它是独立于已有内容之外的。
但你并不真的想把名字包含在内容中。你想要的是,告诉这个名字,它应当位于它的有效空间的左下角,然后将 spec 拉伸到占满整个有效空间。

九: ASRelativeLayoutSpec介绍

你真正需要的是ASRelativeLayoutSpec
ASRelativeLayoutSpec 用一个ASLayoutElement 作为子对象,并把这个空间作为它的有效空间,然后根据你的需要放置子对象。
定义相对规范时,可以设置其verticalPositionhorizo​​ntalPosition属性。
这两个属性可以是以下之一:

  • ASRelativeLayoutSpecPositionStart
  • ASRelativeLayoutSpecPositionCenter
  • ASRelativeLayoutSpecPositionEnd

这些值可以组合,允许你把对象放在某个角、某条变或者它的有效空间的中央。
给你布置个作业,你能将这只青蛙放在它的有效空间的右边吗?


image.png

如果你回答“将verticalPostion 设为ASRelativeLayoutSpecPositionCenter,将horizontalPosition 设为ASRelativeLayoutSpecPositionEnd就对了!

现在再来点难一点的,下面这句将更有感觉。在nameOverlaySpec一句前加入这行:

ASRelativeLayoutSpec *relativeSpec = [ASRelativeLayoutSpec    
      relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionStart       
                                      verticalPosition:ASRelativeLayoutSpecPositionEnd 
                                          sizingOption:ASRelativeLayoutSpecSizingOptionDefault 
                                                 child:self.animalNameTextNode];

如你所见,我们将子对象的horiazontalPosition 设为startverticalPosition设为end。用青蛙的话来说,它应该是这样:

现在你已经创建了一个relative spec,将nameOverlaySpec 定义修改为:

ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec 
                                           overlayLayoutSpecWithChild:gradientOverlaySpec 
                                                              overlay:relativeSpec];

运行 app,你会看见:
好了!cell 的上半部分只剩下一件事情要干了。

十: ASInsetLayoutSpec 介绍

最后一件事情是让动物名称左边留白 16 像素,下边留白 8 个像素。这需要用到ASInsetLayoutSpec
要在对象的四边添加留白,只需将对象放到 inset spec 中,并提供 UIEdgeInsets 来指定需要留白多少像素。

nameOverlaySpec 之后添加一句:

ASInsetLayoutSpec *nameInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 16.0, 8.0, 0.0) child:nameOverlaySpec];

然后,将return 语句修改成返回最外层的spec

return nameInsetSpec;

运行程序,你会看到:

你不需要将 inset 应用到整个 overlay 所包含的空间,因为它还包含了动物图片。
你真正的目的是将 inset 应用到relativeSpec 的有效空间。要解决这个问题,首先删除当前的nameInsetSpec 定义。

然后,在 nameOverlaySpec 定义之前加入这个修改过的版本:

ASInsetLayoutSpec *nameInsetSpec = [ASInsetLayoutSpec 
            insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 16.0, 8.0, 0.0) child:relativeSpec];

现在,你需要让nameOverlaySpec 去包含inset spec,而不是relativeSpec。将原来的nameOverlaySpec 声明修改为:

ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:gradientOverlaySpec overlay:nameInsetSpec];

然后修改 return 语句:

return nameOverlaySpec;

运行 app,正是你希望看到的:

十一: 下半部分

下半部分非常简单,将动物的介绍用一个inset 包围住… 你懂的。
return 语句之前用动物介绍创建一个inset spec

ASInsetLayoutSpec *descriptionTextInsetSpec = [ASInsetLayoutSpec 
insetLayoutSpecWithInsets:UIEdgeInsetsMake(16.0, 28.0, 12.0, 28.0)  child:self.animalDescriptionTextNode];

当你返回这个 spec 并运行 app,你会看到:
这正是你想要的。现在你已经弄好上下两部分了,将它们合在一起是小菜一碟。

十二: 固有内容尺寸

你也许注意到了,你不需要担心文字的内容的尺寸如何填充这个空间。因为ASTextNode 有一个固有内容尺寸 (intrinsic content size),它会根据 text 属性和 attributes 属性自动计算。
下列节点没有默认尺寸:

  • ASDisplayNode 子类
  • ASNetworkImageNode 和 ASMultiplexImageNode
  • ASVideoNode 和 ASVideoPlayerNode

通常这些节点一开始都没有内容,因此无法定义它们自己的大小。这些节点要么需要设置preferredFrameSize,要么在它们拥有具体 size 之前放到一个layout spec 中。

十三: ASStackLayoutSpec介绍

该说说stack layout spec 了。你可以把它看成是一种和UIStackView 类似的layout spec,但它能够自动向后兼容,这确实太爽了。 Stacks 可以定义要么横向要么纵向,和其它layout spec 一样,它可以把其它节点或layout spec 作为子对象。

为了使用stack spec,在description inset 一句后加入:

ASStackLayoutSpec *verticalStackSpec = [[ASStackLayoutSpec alloc] init];
verticalStackSpec.direction = ASStackLayoutDirectionVertical;
verticalStackSpec.children = @[nameOverlaySpec, descriptionTextInsetSpec];

这里创建了一个 stack,方向设置为 vertical,然后将上下两部分添加进去。

同样,返回新的layou spec:

return verticalStackSpec;

运行程序,马上就大功告成了

注意:前面说过,stack 是核心 layout spec 中比较简单的一个。大部分布局都能用一种 stack 或一系列嵌套的 stack 来进行表达。

将 stack 进行嵌套,可以让 stack 变得变化无穷、极其麻烦。要深入了解,请参考flex box froggy game 以及 Async Display Kit 文档

十四: ASBackgroundLayoutSpec 介绍

还记得你的老朋友 overlay spec吗?它有一个原则是:在一个overlay spec 中,被覆盖的对象必须有一个 size。

在后面的对象定义一个 size,而前面的对象将被拉伸并占据它的大小。

background spec 则截然相反。如果你有一个对象能够确定自己的大小,你想在它下面拉伸出另一个对象,你可以使用background spec

例如,你可以用background layout spec 将模糊的动物图片拉伸后放到整个 stack 的后面。
要这样做,需要增加这行:

ASBackgroundLayoutSpec *backgroundLayoutSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:verticalStackSpec background:self.backgroundImageNode];

然后将 return 语句修改为:

return backgroundLayoutSpec;

运行 app,查看最后的效果:


image.png

十五: 结束

这里下载完成后的项目。另外,这里可以下载 Swift 项目。

当你熟悉了这些概念之后,有一个学习更多内容的好地方就是这里。这真的是一道关于布局系统能做什么的美味大餐。

希望你喜欢本教程,有任何关于布局的问题,请在下面留言。

相关文章

  • ASDK的使用(二): 自动布局

    在AsyncDisplayKit2.0 教程的第二部分中,了解在iOS应用程序中构建快速灵活的布局是多么容易。 在...

  • 03.ASDK整理:常用布局

    ASDK布局使用Flexbox方式布局。 Flexbox 验证:http://nguyenhuy.github.i...

  • 预加载与智能预加载(iOS)

    前两次的分享分别介绍了 ASDK 对于渲染的优化以及 ASDK 中使用的另一种布局模型;这两个新机制的引入分别解决...

  • 预加载与智能预加载(iOS)

    前两次的分享分别介绍了 ASDK 对于渲染的优化以及 ASDK 中使用的另一种布局模型;这两个新机制的引入分别解决...

  • 【OC梳理】自动布局

    自动布局基础篇 关于自动布局的基本使用,参考网上的文章即可,如:iOS开发-自动布局篇:史上最牛的自动布局教学! ...

  • Third Party

    A:推荐使用 B:修改使用 C:参考使用 自动布局Masonry(A)iOS自动布局框架-Masonry详解SDA...

  • iOS 界面适配右到左语言

    方向判断 1.自动布局. 建议使用自动布局,并且使用Leading和trailing,不建议使用left和righ...

  • ASDK布局快速布局(四)

    快速开始 1.1: Motivation & Benefits 对于复杂的视图层次结构来说,UIKit的自动布局会...

  • iOS自动布局

    使用自动布局的步骤是: 1、使用代码自动布局(autolayout) frame就会失效,所以 不需要再去设置视...

  • Flex——告别CSS布局

    Flex 布局可以实现空间自动分配、自动对齐Flex 适用于简单的线性布局,复杂布局使用 Grid 布局注意:设为...

网友评论

    本文标题:ASDK的使用(二): 自动布局

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