【译】Flutter _ 深入理解布局约束

前言

这篇文章最初来自于 Marcelo Glasberg 在 Medium 发表的 Flutter: The Advanced Layout Rule Even Beginners Must Know。后被 Flutter Team 发现并收录到 flutter.dev

在认真阅读完这篇文章后,我认为它对 Flutter 开发者来说具有相当的 指导意义,每一位 Flutter 开发都应该认真理解其中的布局约束过程,是非常必要的!因此,在翻译本文的过程中,我们对译文反复打磨,尽可能保留原文向传递给读者的内容。希望让每一位看到此文的开发者都能够有所收获。

深入理解布局约束

我们会经常听到一些开发者在学习 Flutter 时的疑惑:为什么我设置了 width:100, 但是看上去却不是 100 像素宽呢。(注意,本文中的“像素”均指的是逻辑像素) 通常你会回答,将这个 Widget 放进 Center 中,对吧?

别这么干。

如果你这样做了,他们会不断找你询问这样的问题:为什么 FittedBox 又不起作用了? 为什么 Column 又溢出边界,亦或是 IntrinsicWidth 应该做什么。

其实我们首先应该做的,是告诉他们 Flutter 的布局方式与 HTML 的布局差异相当大 (这些开发者很可能是 Web 开发),然后要让他们熟记这条规则:

  • 首先,上层 widget 向下层 widget 传递约束条件。

  • 然后,下层 widget 向上层 widget 传递大小信息。

  • 最后,上层 widget 决定下层 widget 的位置。

如果我们在开发时无法熟练运用这条规则,在布局时就不能完全理解其原理,所以越早掌握这条规则越好!

更多细节:

  • Widget 会通过它的 父级 获得自身的约束。 约束实际上就是 4 个浮点类型的集合: 最大/最小宽度,以及最大/最小高度。

  • 然后,这个 widget 将会逐个遍历它的 children 列表。向子级传递 约束(子级之间的约束可能会有所不同),然后询问它的每一个子级需要用于布局的大小。

  • 然后,这个 widget 就会对它子级的 children 逐个进行布局。 (水平方向是 x 轴,竖直是 y 轴)

  • 最后,widget 将会把它的大小信息向上传递至父 widget(包括其原始约束条件)。

例如,如果一个 widget 中包含了一个具有 padding 的 Column, 并且要对 Column 的子 widget 进行如下的布局:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

那么谈判将会像这样:

Widget: “嘿!我的父级。我的约束是多少?”

Parent: “你的宽度必须在 80300 像素之间,高度必须在 3085 之间。”

Widget: “嗯…我想要 5 个像素的内边距,这样我的子级能最多拥有 290 个像素宽度和 75 个像素高度。”

Widget: “嘿,我的第一个子级,你的宽度必须要在 0290,高度在 075 之间。”

First child: “OK,那我想要 290 像素的宽度,20 个像素的高度。”

Widget: “嗯…由于我想要将我的第二个子级放在第一个子级下面,所以我们仅剩 55 个像素的高度给第二个子级了。”

Widget: “嘿,我的第二个子级,你的宽度必须要在 0290,高度在 055 之间。”

Second child: “OK,那我想要 140 像素的宽度,30 个像素的高度。”

Widget: “很好。我的第一个子级将被放在 x: 5 & y: 5 的位置, 而我的第二个子级将在 x: 80 & y: 25 的位置。”

Widget: “嘿,我的父级,我决定我的大小为 300 像素宽度,60 像素高度。”

限制

正如上述所介绍的布局规则中所说的那样, Flutter 的布局引擎有一些重要限制:

  • 一个 widget 仅在其父级给其约束的情况下才能决定自身的大小。 这意味着 widget 通常情况下 不能任意获得其想要的大小

  • 一个 widget 无法知道,也不需要决定其在屏幕中的位置。 因为它的位置是由其父级决定的。

  • 当轮到父级决定其大小和位置的时候,同样的也取决于它自身的父级。 所以,在不考虑整棵树的情况下,几乎不可能精确定义任何 widget 的大小和位置。

样例

下面的示例由 DartPad 提供,具有良好的交互体验。 使用下面水平滚动条的编号切换 29 个不同的示例。

你可以在 flutter.cn 上找到该源码。

如果你愿意的话,你还可以在 这个 Github 仓库中 获取其代码。

以下各节将介绍这些示例。

样例 1

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Container(color: Colors.red)

整个屏幕作为 Container 的父级,并且强制 Container 变成和屏幕一样的大小。

所以这个 Container 充满了整个屏幕,并绘制成红色。

样例 2

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Container(width: 100, height: 100, color: Colors.red)

红色的 Container 想要变成 100 x 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。 相反,它将会在允许的空间内,把 Container 放在右下角(bottomRight)。

样例 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 没有固定大小但是有子级,所以它决定变成它 child 的大小。

然后红色的 Container 告诉它的 child 可以变成任意大小,但是不能超出屏幕。

而它的 child 是一个想要 30 × 30 大小绿色的 Container。由于红色的 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 带入了约束的计算中。 所以它有一个 30 x 30 的外边距。由于这个外边距,所以现在你能看见红色了。 而绿色的 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 仅对其从其父级接收到的约束下施加其他约束。

在这里,屏幕迫使 ConstrainedBox 与屏幕大小完全相同, 因此它告诉其子 Widget 也以屏幕大小作为约束, 从而忽略了其 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 达到屏幕可允许的任意大小。 ConstrainedBoxconstraints 参数带来的约束附加到其子对象上。

Container 必须介于 70 到 150 像素之间。虽然它希望自己有 10 个像素大小, 但最终获得了 70 个像素(最小为 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 达到屏幕可允许的任意大小。 ConstrainedBoxconstraints 参数带来的约束附加到其子对象上。

Container 必须介于 70 到 150 像素之间。 虽然它希望自己有 1000 个像素大小, 但最终获得了 150 个像素(最大为 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 达到屏幕可允许的任意大小。 ConstrainedBoxconstraints 参数带来的约束附加到其子对象上。

Container 必须介于 70 到 150 像素之间。 虽然它希望自己有 100 个像素大小, 因为 100 介于 70 至 150 的范围内,所以最终获得了 100 个像素。

样例 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 可以变为任意大小。

不幸的是,在这种情况下,容器的宽度为 4000 像素, 这实在是太大,以至于无法容纳在 UnconstrainedBox 中, 因此 UnconstrainedBox 将显示溢出警告(overflow warning)。

样例 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 允许其子容器设置为任意大小。

OverflowBoxUnconstrainedBox 类似,但不同的是, 如果其子级超出该空间,它将不会显示任何警告。

在这种情况下,容器的宽度为 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,
)
)
)

这次你就不会遇到报错了。 UnconstrainedBoxLimitedBox 一个无限的大小; 但它向其子级传递了最大为 100 的约束。

如果你将 UnconstrainedBox 替换为 Center, 则LimitedBox 将不再应用其限制(因为其限制仅在获得无限约束时才适用), 并且容器的宽度允许超过 100。

上面的样例解释了 LimitedBoxConstrainedBox 之间的区别。

样例 18

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

FittedBox(
child: Text(‘Some Example Text.’),
)

屏幕强制 FittedBox 变得和屏幕一样大, 而 Text 则是有一个自然宽度(也被称作 intrinsic 宽度), 它取决于文本数量,字体大小等因素。

FittedBoxText 可以变为任意大小。 但是在 Text 告诉 FittedBox 其大小后, FittedBox 缩放文本直到填满所有可用宽度。

样例 19

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Center(
child: FittedBox(
child: Text(‘Some Example Text.’),
)
)

但如果你将 FittedBox 放进 Center widget 中会发生什么? Center 将会让 FittedBox 能够变为任意大小, 取决于屏幕大小。

FittedBox 然后会根据 Text 调整自己的大小, 然后让 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.’),
)

然而,如果你删除了 FittedBoxText 则会从屏幕上获取其最大宽度, 并在合适的地方换行。

样例 22

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

在这里小编整理了一份Android大厂常见面试题,和一些Android架构视频解析,都已整理成文档,全部都已打包好了,希望能够对大家有所帮助,在面试中能顺利通过。

image

image

喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

源码讲义、实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

在这里小编整理了一份Android大厂常见面试题,和一些Android架构视频解析,都已整理成文档,全部都已打包好了,希望能够对大家有所帮助,在面试中能顺利通过。

[外链图片转存中…(img-qkl1aGJC-1713440707977)]

[外链图片转存中…(img-zzzoZc5X-1713440707977)]

喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值