写点什么

Flutter 初学者必读的高级布局规则

  • 2020-08-06
  • 本文字数:7546 字

    阅读完需:约 25 分钟

Flutter初学者必读的高级布局规则

假设有人正在学习 Flutter,他问你为什么有的 width:100 的 widget 宽度不是 100 像素,标准答案是让他将 widget 放在一个 Center 里面,对吗?


别这么做。


如果你这么回答他,他就会一次又一次跑回来问你新的问题,比如说为什么某些 FittedBox 无法正常工作,为什么那个 Column 溢出,或者 IntrinsicWidth 是用来做什么的,诸如此类。


这时候你应该告诉他:Flutter 布局与 HTML 布局(他之前可能接触的就是后者)有着很大不同,然后让他记住以下规则:


约束(Constraints)在下面,大小(Sizes)在上面。位置(Positions)由父项(Parents)决定。


想要真正理解 Flutter 的布局,就得搞清楚上面这条规则,所以大家都应该尽早学会它。


具体来说:


  • widget 从其父项获得自己的约束。一个“约束”是由 4 个 double 值组成的:分别是最小和最大宽度,以及最小和最大高度。

  • 然后,widget 会遍历自己的子项(children)列表。widget 会逐个向每个子项告知它们的约束(各个子项的约束可以是不同的),然后询问每个子项想要设置的大小。

  • 接下来,widget 一个个确定子项位置(在 x 轴上确定水平位置,在 y 轴上确定垂直位置)。

  • 最后,widget 将其自身大小告知父项(当然这个大小也要符合原始约束)。


例如,如果一个 widget 是一个带有一些 padding 的 column,并且想要布局自己的两个子项:


Widget:你好父项,我的约束是什么?


父项:你的宽度必须在 90 到 300 像素之间,高度在 30 到 85 像素之间。


Widget:我想有 5 像素的 padding,所以我的子项最多有 290 像素的宽度和 75 像素的高度。


Widget:你好第一个子项,你的宽度必须在 0 到 290 像素之间,高度在 0 到 75 像素之间。


第一个子项:好的,那么我希望自己的宽度是 290 像素,高度为 20 像素。


Widget:那么,因为我想将第二个子项放在第一个子项之下,因此第二个子项只剩下 55 像素的高度。


Widget:你好第二个子项,你的宽度必须介于 0 到 290 像素之间,并且高度必须介于 0 到 55 像素之间。


第二个子项:好吧,我希望宽度是 140 像素,高 30 像素。


Widget:很好。我将把第一个子项放在 x: 5 和 y: 5 的位置,将第二个子项放在 x: 80 和 y: 25 的位置。


Widget:你好父项,我决定将自己设为 300 像素宽和 60 像素高。

限制

因为上述布局规则的关系,Flutter 的布局引擎有一些重要的限制:


  • 一个 widget 只能在其父项赋予的约束内决定其自身的大小。这意味着 widget 往往不能自由决定自己的大小

  • widget 不知道,也无法确定自己在屏幕上的位置,因为它的位置是由父项决定的。

  • 由于父项的大小和位置又取决于上一级父项,因此只有考虑整个树才能精确定义每个 widget 的大小和位置。

示例

可以运行这个DartPad来观察每个示例的效果。另外可以从这个GitHub存储库中获取最新代码。

示例 1

Container(color: Colors.red)
复制代码


屏幕是 Container 的父项。它强制红色的 Container 与屏幕大小完全相同。


这样 Container 就会填满整个屏幕,并且全都变成红色。

示例 2

Container(width: 100, height: 100, color: Colors.red)
复制代码


红色的 Container 想要设为 100×100 的大小,但这是不行的,因为屏幕会强制使其大小与屏幕完全相同。


因此,Container 将填满整个屏幕。

示例 3

Center(   child: Container(width: 100, height: 100, color: Colors.red))
复制代码


屏幕会强制将 Center 设置为与屏幕大小完全相同。因此 Center 将填满整个屏幕。


Center 告诉 Container,后者的大小不能超出屏幕。现在,Container 就可以是 100×100。

示例 4

Align(   alignment: Alignment.bottomRight,   child: Container(width: 100, height: 100, color: Colors.red),)
复制代码


这与前面的示例不同之处是使用了 Align 代替 Center。


Align 还告诉 Container,后者的大小可以自由决定,但是如果有空白空间,它不会让 Container 居中,而是将其对齐到可用空间的右下角。

示例 5

Center(   child: Container(      color: Colors.red,      width: double.infinity,      height: double.infinity,   ))
复制代码


屏幕会强制将 Center 设置为与屏幕大小完全相同。因此 Center 将填满整个屏幕。


Center 告诉 Container,后者的大小不能超出屏幕。Container 希望具有无限大的尺寸,但由于存在前述约束,因此它只能填满屏幕。

示例 6

Center(child: Container(color: Colors.red))
复制代码


屏幕会强制将 Center 设置为与屏幕大小完全相同。因此 Center 将填满整个屏幕。


Center 告诉 Container,后者的大小不能超出屏幕。由于 Container 没有子项且没有固定大小,因此它决定要尽可能变大,结果就填满了屏幕。


但为什么 Container 要这样决定呢?因为这是 Container widget 的创建者的设计决策。它也可能会有其他设计,所以你需要阅读 Container 的文档以了解它在不同情况下的行为方式。

示例 7

Center(   child: Container(      color: Colors.red,      child: Container(color: Colors.green, width: 30, height: 30),   ))
复制代码


屏幕会强制将 Center 设置为与屏幕大小完全相同。因此 Center 将填满整个屏幕。


Center 告诉红色 Container,后者的大小不能超出屏幕。由于红色 Container 没有大小,但有一个子项,因此它决定要与子项的大小相同。


红色的 Container 告知其子项,后者的大小不能超出屏幕。


这个子项恰好是一个绿色的 Container,希望自己的大小是 30×30。如上所述,红色的 Container 会将自己的大小设为子项的大小,因此它也会是 30×30。结果红色是显示不出来的,因为绿色的 Container 会完全覆盖红色的 Container。

示例 8

Center(   child: Container(     color: Colors.red,     padding: const EdgeInsets.all(20.0),     child: Container(color: Colors.green, width: 30, height: 30),   ))
复制代码


红色的 Container 会根据子项的大小设置自己的大小,但同时会考虑自己的 padding。因此它将是 70×70(=30×30 加上各个面的 20 像素 padding)。由于存在 padding,因而红色将是可见的,绿色的 Container 的大小与上一个示例中的相同。

示例 9

ConstrainedBox(   constraints: BoxConstraints(      minWidth: 70,       minHeight: 70,      maxWidth: 150,       maxHeight: 150,   ),   child: Container(color: Colors.red, width: 10, height: 10),)
复制代码


你可能会以为 Container 会是 70 到 150 像素之间,但是你错了。ConstrainedBox 只会在 widget 从父项获得的约束基础之上施加额外的约束。


在这里,屏幕将 ConstrainedBox 强制为与屏幕大小完全相同,因此它将告诉自己的子 Container 也不能超出屏幕大小,这样就忽略了它的 constraints 参数。

示例 10

Center(   child: ConstrainedBox(      constraints: BoxConstraints(         minWidth: 70,          minHeight: 70,         maxWidth: 150,          maxHeight: 150,      ),      child: Container(color: Colors.red, width: 10, height: 10),   )    )
复制代码


现在,Center 将允许 ConstrainedBox 的大小最大不能超出屏幕。ConstrainedBox 将从其 constraints 参数中为其子项施加额外的约束。


因此,Container 必须介于 70 到 150 像素之间。它希望自己是 10 个像素,所以结果会是 70 像素(最小约束值)。

示例 11

Center(  child: ConstrainedBox(     constraints: BoxConstraints(        minWidth: 70,         minHeight: 70,        maxWidth: 150,         maxHeight: 150,        ),     child: Container(color: Colors.red, width: 1000, height: 1000),  )  )
复制代码


Center 将允许 ConstrainedBox 的大小最大不能超出屏幕。ConstrainedBox 将从其 constraints 参数中为其子项施加额外的约束。


因此,Container 必须介于 70 到 150 像素之间。它希望自己是 1000 个像素,所以最后会是 150 像素(最大约束值)。

示例 12

Center(   child: ConstrainedBox(      constraints: BoxConstraints(         minWidth: 70,          minHeight: 70,         maxWidth: 150,          maxHeight: 150,      ),      child: Container(color: Colors.red, width: 100, height: 100),   ) )
复制代码


Center 将允许 ConstrainedBox 的大小最大不能超出屏幕。ConstrainedBox 将从其 constraints 参数中为其子项施加额外的约束。


因此,Container 必须介于 70 到 150 像素之间。它希望自己是 100 像素,结果就会是这个大小,因为这个值介于 70 到 150 之间。

示例 13

UnconstrainedBox(   child: Container(color: Colors.red, width: 20, height: 50),)
复制代码


屏幕强制 UnconstrainedBox 与屏幕大小完全相同。但是,UnconstrainedBox 允许其 Container 子项自由设定大小。

示例 14

UnconstrainedBox(   child: Container(color: Colors.red, width: 4000, height: 50),);
复制代码


屏幕强制 UnconstrainedBox 与屏幕大小完全相同,UnconstrainedBox 允许 Container 子项自由设定大小。


不幸的是,在这个例子中 Container 的宽度为 4000 像素,因为太大而无法容纳在 UnconstrainedBox 中,因此 UnconstrainedBox 将显示让人胆战心惊的“溢出警告”。

示例 15

OverflowBox(   minWidth: 0.0,   minHeight: 0.0,   maxWidth: double.infinity,   maxHeight: double.infinity,      child: Container(color: Colors.red, width: 4000, height: 50),);
复制代码


屏幕强制 OverflowBox 与屏幕大小完全相同,并且 OverflowBox 允许 Container 子项自由设定大小。


这里的的 OverflowBox 与 UnconstrainedBox 相似,不同之处在于,如果子项超出了它的范围,它也不会显示任何警告。


在这个例子中下,Container 的宽度为 4000 像素,因为太大而无法容纳在 OverflowBox 中,但是 OverflowBox 只会显示自己能显示的部分,而不会发出警告。

示例 16

UnconstrainedBox(   child: Container(      color: Colors.red,       width: double.infinity,       height: 100,   ))
复制代码


这不会渲染任何内容,并且你会在控制台中收到错误消息。


UnconstrainedBox 允许其子项自由设定大小,但是其 Container 子项的大小是无限的。


Flutter 无法渲染无限的大小,因此会显示以下错误消息:BoxConstraints forces an infinite width。

示例 17

UnconstrainedBox(   child: LimitedBox(      maxWidth: 100,      child: Container(          color: Colors.red,         width: double.infinity,          height: 100,      )   ))
复制代码


这里你不会再遇到错误,因为当 UnconstrainedBox 为 LimitedBox 赋予一个无限的大小时,后者将向自己的子项传递 100 的宽度上限。


请注意,如果将 UnconstrainedBox 更改为 Center widget,则 LimitedBox 就不会再应用自己的限制(因为其限制仅在约束为无限时才会应用),并且 Container 的宽度将被允许超过 100。


这清楚表明了 LimitedBox 和 ConstrainedBox 之间的区别。

示例 18

FittedBox(   child: Text('Some Example Text.'),)
复制代码


屏幕强制 FittedBox 与屏幕大小完全相同。Text 将有一些自然宽度(也称为其固有宽度),该宽度取决于文本的数量和字体大小等。


FittedBox 将让 Text 自由设定大小,但是在 Text 将其大小告知 FittedBox 之后,FittedBox 会对其进行缩放,使其填满可用宽度。

示例 19

Center(   child: FittedBox(      child: Text('Some Example Text.'),   ))
复制代码


但是,如果将 FittedBox 放在 Center 内会怎样?Center 会让 FittedBox 的大小最大不能超出屏幕。


然后,FittedBox 会将其自身调整为 Text 的大小,并让 Text 自由设定大小。由于 FittedBox 和 Text 的大小相同,因此不会发生缩放。

示例 20

Center(   child: FittedBox(      child: Text('This is some very very very large text that is too big to fit a regular screen in a single line.'),   ))
复制代码


但是,如果 FittedBox 位于 Center 内部,但 Text 太大而超出了屏幕该怎么办?


FittedBox 将尝试让自己和 Text 一样大,但它不能超出屏幕。然后,它会设定和屏幕大小一样的目标,并调整 Text 的大小以使其也适合屏幕。

示例 21

Center(   child: Text('This is some very very very large text that is too big to fit a regular screen in a single line.'),)
复制代码


但是,如果我们移除 FittedBox,则 Text 将从屏幕获得自己的最大宽度,并且会换行来适合屏幕宽度。

示例 22

FittedBox(   child: Container(      height: 20.0,       width: double.infinity,   ))
复制代码


注意 FittedBox 只能缩放有界的 widget(宽度和高度都不是无限的)。否则,它将无法渲染任何内容,并且你会在控制台中收到错误消息。

示例 23

Row(   children:[      Container(color: Colors.red, child: Text('Hello!')),      Container(color: Colors.green, child: Text('Goodbye!)),   ])
复制代码


屏幕强制 Row 与屏幕大小完全相同。


就像 UnconstrainedBox 一样,Row 不会对其子项施加任何约束,而是让它们自由设定大小。然后 Row 会将子项并排放置,并且空下剩余的空间。

示例 24

Row(   children:[      Container(color: Colors.red, child: Text('This is a very long text that won’t fit the line.')),      Container(color: Colors.green, child: Text('Goodbye!')),   ])
复制代码


由于 Row 不会对其子项施加任何约束,因此子项可能会太大而超出了可用的 Row 宽度。在这种情况下,就像 UnconstrainedBox 一样,Row 将显示“溢出警告”。

示例 25

Row(   children:[      Expanded(         child: Container(color: Colors.red, child: Text('This is a very long text that won’t fit the line.'))      ),      Container(color: Colors.green, child: Text('Goodbye!')),   ])
复制代码


当一个 Row 子项包装在一个 Expanded widget 中时,Row 将不再允许该子项定义自己的宽度。


相反,它将根据其他子项定义 Expanded 的宽度,只有这样 Expanded widget 才会强制原始子项的宽度与 Expanded 相同。


换句话说,一旦你使用了 Expanded,原始子项的宽度就不重要了,并且将被忽略。

示例 26

Row(   children:[      Expanded(         child: Container(color: Colors.red, child: Text(‘This is a very long text that won’t fit the line.’)),      ),      Expanded(         child: Container(color: Colors.green, child: Text(‘Goodbye!’),      ),   ])
复制代码


如果所有 Row 子项都包装在 Expanded widget 中,则每个 Expanded 的大小将与其 flex 参数成比例,只有这样,每个 Expanded widget 才会强制其子项的宽度等于 Expanded。


换句话说,Expanded 会忽略其子项的首选宽度。

示例 27

Row(children:[  Flexible(    child: Container(color: Colors.red, child: Text('This is a very long text that won’t fit the line.'))),  Flexible(    child: Container(color: Colors.green, child: Text(‘Goodbye!’))),  ])
复制代码


如果使用 Flexible 代替 Expanded,则唯一的区别是 Flexible 将使其子项的宽度小于等于 Flexible 自身,而 Expanded 会强制其子项的宽度和 Expanded 完全相同。


但是,Expanded 和 Flexible 在调整自己的大小时都会忽略自己子项的宽度。


请注意,这意味着我们无法按大小比例扩展 Row 子项。Row 要么使用与子项相同的宽度,或者在使用 Expanded 或 Flexible 时完全忽略子项。

示例 28

Scaffold(   body: Container(      color: blue,      child: Column(         children: [            Text('Hello!'),            Text('Goodbye!'),         ]      )))
复制代码


屏幕会强制 Scaffold 与屏幕完全相同。因此 Scaffold 会填满屏幕。


Scaffold 告诉 Container,后者不能超出屏幕大小。


注意:当 widget 告诉其子项可以小于某个特定大小时,我们说该 widget 为其子项提供了“宽松”的约束。稍后会进一步说明。

示例 29

Scaffold(   body: SizedBox.expand(      child: Container(         color: blue,         child: Column(            children: [               Text('Hello!'),               Text('Goodbye!'),            ],         ))))
复制代码


如果我们希望 Scaffold 的子项大小与 Scaffold 本身完全相同,则可以将其子项包装到一个 SizedBox.expand 中。


注意:当 widget 告诉其子项必须等于某个大小时,我们说该 widget 为其子项提供了“严格”的约束。

严格×宽松约束

我们经常听到某些约束是“严格”或“宽松”的说法,因此这里讲讲它们的含义。


严格的约束只提供了一种可能性:一个确定的大小。换句话说,严格约束的最大宽度等于其最小宽度,并且其最大高度等于最小高度。


转到 Flutter 的 box.dart 文件并搜索 BoxConstraints 构造器,你会发现以下内容:


BoxConstraints.tight(Size size)   : minWidth = size.width,     maxWidth = size.width,     minHeight = size.height,     maxHeight = size.height;
复制代码


再次回顾上面的示例 2,它告诉我们屏幕强制红色的 Container 与屏幕尺寸完全相同。当然,屏幕是将严格的约束传递给 Container 来实现这一点的。


另一方面,宽松的约束可设置最大宽度/高度,但允许 widget 自由取小于这个值的大小。换句话说,宽松约束的最小宽度/高度都等于


BoxConstraints.loose(Size size)   : minWidth = 0.0,     maxWidth = size.width,     minHeight = 0.0,     maxHeight = size.height;

复制代码


重新查看示例 3,它告诉我们:Center 让红色的 Container 大小不能大于屏幕。Center 将宽松的约束传递给 Container 来做到这一点。最终,Center 的主要目的是将其从父项(屏幕)获得的严格约束转换为对其子项(Container)的宽松约束。

学习特定 widget 的布局规则

我们需要了解通用的布局规则,但光是这样这还不够。


每个 widget 在应用通用规则时都有很大的自由度,因此只看 widget 的名称是没法知道它会做什么事情的。


如果你只靠猜测的话可能会猜错。除非你已阅读过 widget 的文档或研究了其源代码,否则你无法知道 widget 的确切行为。


布局源码往往是很复杂的,因此最好去看它们的文档。但是如果你决定要研究布局的源码,则可以使用 IDE 的导航功能轻松找到它。


下面是一个示例:


  • 在你的代码中找到一些 Column,然后导航到其源代码(IntelliJ 中按下 Ctrl-B)。你将被带到 basic.dart 文件。由于 Column 扩展了 Flex,因此请导航至 Flex 源代码(也位于 basic.dart 中)。

  • 现在向下滚动,直到找到一个名为 createRenderObject 的方法。如你所见,此方法返回一个 RenderFlex。这是和 Column 对应的渲染对象。现在导航到 RenderFlex 的源代码,IDE 会带你进入 flex.dart 文件。

  • 现在向下滚动,直到找到一个名为 performLayout 的方法。这就是为 Column 布局的方法。


非常感谢Simon Lightfoot校对本文,提供标题图片并为本文提供内容建议。


备注:本文已加入Flutter官方文档


参考阅读:


https://medium.com/flutter-community/flutter-the-advanced-layout-rule-even-beginners-must-know-edc9516d1a2


2020-08-06 14:182314

评论

发布
暂无评论
发现更多内容

作业一

Geek_408c99

vivo鲁京辉:数据安全与隐私保护是消费者的基本权利,是企业的“铁律”

Geek_2d6073

程序员未来职业规划路线,请收藏

收到请回复

Java 程序员 架构 职业规划 语言 & 开发

架构实战营模块一作业

π

架构实战营

【编程实践】详解 MySQL 在 Python 中的使用(2)-pymysql的使用

迷彩

MySQL 数据库 增删改查 pymysql 9月月更 数据库操作

微信业务架构图&“学生管理系统”毕设架构设计

Louis

Groovy closure 与Java function转换

FunTester

天天都在谈的防火墙到底是个啥,有哪些分类?如何选择防火墙?

wljslmz

网络安全 防火墙 9月月更

什么是地址转换协议ARP?工作流程是什么样的?

wljslmz

9月月更 ARP

01简单架构分析

神奇的叶叔叔

开发者有话说|要不是家里穷,我也不想当码农

三掌柜

个人成长 个人感悟

2022-09-28:以下go语言代码输出什么?A:1 1;B:1 2;C:2 2;D:不确定。 package main import ( “fmt“ ) func main() { var

福大大架构师每日一题

golang 福大大 选择题

数据开发也能双轮驱动?

乌龟哥哥

9月月更

APICloud可视化编程(二)

YonBuilder低代码开发平台

前端开发 APICloud 多端开发

史上最全的Java容器集合之equals 和 hashCode

自然

集合 Java core 9月月更

详解数据计算能力的四种类型

穿过生命散发芬芳

9月月更 数据计算

Python语法之异常处理

芯动大师

异常处理 9月月更 Python异常处理方法

挑战Python的语法练习

芯动大师

项目实战 9月月更 模块创建

Java | abstract关键字【面向对象的第三大特征——多态】

Fire_Shield

Java 9月月更 abstract

史上最全的Java容器集合之HashMap(源码解读)

自然

集合 Java core 9月月更

作业一

小虎

架构实战营

极客时间-架构师训练营作业-模块一

沐の爹

架构实战训练营模块 1 作业

atcgnu

模块一作业

愚人夜行者

Python语法之模块和包(2)

芯动大师

9月月更 模块创建 包的应用

【最右】面向TS生态的新型Flutter框架

刘剑

typescript 小程序 移动端 动态化 flutter for web

后疫情时代,RTE“沉浸式”体验还能这么玩?丨RTE 2022 编程挑战赛赛后专访

声网

人工智能

Linux下通过tar包方式安装MySQL,详细教程

阿柠xn

运维 MySQL 运维 Linux tar 9月月更

人工智能知识介绍

阿柠xn

人工智能 AI 科普 9月月更

架构实战训练营模块 1 作业

Geek_b35d92

架构训练

网络中一些很常见的协议,以及他们对应的报文格式介绍

阿柠xn

TCP 网络协议 9月月更 ARP

Flutter初学者必读的高级布局规则_大前端_Marcelo Glasberg_InfoQ精选文章