Flutter布局之——理解Constraints
当学习Flutter的人问你为什么width:100
的widget不是100像素宽时,默认的答案是告诉他widget放在Center里面,对吧?(不知原理,答非所问)
千万不要那样做
如果你这样做,他们会一次又一次地回来,问为什么有些FittedBox
不工作,为什么Column
溢出,或者IntrinsicWidth
应该做什么?
相反,首先告诉他们Flutter布局和HTML布局有很大的不同(这可能是他们的来源),然后让他们记住以下规则:
Constraints go down. Sizes go up. Parent sets position
不了解这个规则就无法真正理解Flutter布局,所以Flutter开发放着要早点学习:
更详细的:
- 小部件从其父级获得自己的Constraints。约束只是一组4个:最小和最大宽度,以及最小和最大高度。
- 然后小部件遍历它自己的子列表。小部件一个接一个地告诉它的孩子他们的约束是什么(每个孩子可能不同),然后询问每个孩子它想要的大小。
- 然后,小部件一个接一个地定位其子项(水平方向在x轴上,垂直方向在y轴行)。
- 最后,小部件将自己的大小告知其父级(当然在原始限制范围内)。
例如,如果一个组合小部件爱你包含一个带有一些填充的列,并且想要按如下方式布置它的两个子部件:
谈判是这样的:
- 小部件:”嘿,家长,我的限制是什么?“
- 家长:”你的宽度必须在
80
到300
像素之间,高度必须在30
到85
像素之间。“ - 小部件:”嗯,既然我想要
5
个像素的填充,那么我的孩子最多可以有290
个像素的宽度和75
个像素的高度。“ - 小部件:“嘿,第一个孩子,你的宽度必须在
0
到290
像素之间,高度必须在0
到75
像素之间。” - 第一个孩子:“好的,那我希望宽
290
像素,高20
像素。” - 小部件:“嗯,因为我想把我的第二个孩子放在第一个孩子下面,所以我的第二个孩子只剩下
55
像素的高度。” - 小部件:“嘿 老二,你的宽度必须在
0
到290
之间,高度必须在0
到55
之间。” - 第二个孩子:“好的,我希望宽
140
像素,高30
像素。” - 小部件:“很好。我的第一个孩子的位置是
x:5
和y:5
,我的第二个孩子的位置是x:80
和y:25
。” - 小部件:“嘿,家长,我决定我的尺寸为
300
像素宽,60
像素高。”
1、限制
由于上面提到的布局规则,Flutter的布局引擎有几个重要的限制:
- 小部件只能在其父级刚给定稿的约束范围内决定自己的大小。这意味着小部件通常不能有任何它想要的大小。
- 小部件无法知道也无法决定自己在屏幕的中的位置,因为是小部件的父级决定了小部件的位置。
- 由于父级的大小和位置反过来也取决于它自己的父级,因此不在考虑整个树的情况下不可能精确定义任何小部件的大小和位置。
- 如果一个子项想要与其父项不同的尺寸,而父项没有足够的信息来对其它,那么该子项的尺寸可能会被忽略。定义对齐方式时要具体。
1.1、例子
下面针对上述有29个例子,请客官们慢慢细读,读完了就对Contraints有了进一步的了解。
1.1.1、示例1
Container(color: red)
屏幕是Container
的父级,它强制Container
与屏幕大小完全相同。
所以Container
会填满屏幕并将其涂成红色。
1.1.2、示例2
Container(width: 100, height: 100, color: red)
红色的Container
想要100x100
,但它不能,因为屏幕强制它与屏幕大小完全相同。
所以容器填满了全屏。
1.1.3、示例3
Center(
child: Container(width: 100, height: 100, color: red),
)
屏幕强制Center
与屏幕大小完全相同,因此Center会填满屏幕。
Center
告诉Container
它可以是它想要的任何大小,但不能大于屏幕。现在Contaiiner
确实可以是100x100
。
1.1.4、示例4
Align(
alignment: Alignment.bottomRight,
child: Container(wiidth: 100, height: 100, color: red)
),
这与前面的示例不同,它使用Align
而不是Center
。
Align
还告诉Container
它可以是它想要的任何大小,但如果有空白空间,它不会使Container
居中。相反,它将Container
对齐到可用空间的右下角。
1.1.5、示例5
Center(
child: Container(
width: double.infinity, height: double.infinity, color: red)
)
屏幕强制Center
与屏幕大小完全相同,因此Center
会填满屏幕。
Center
告诉Container
它可以是它想要的任何大小,但不能大于屏幕。Container
想要无限大,但由于它不能大于屏幕,所以它只能填满屏幕。
1.1.6、示例6
Center(
child: Container(color: red),
)
屏幕强制Center
与屏幕大小完全相同,因此Center
会填满屏幕。
Center
告诉Container
它可以是它想要的任何大小,但不能大于屏幕。由于Container
没有子元素,也没有固定的大小,它决定尽可能大,所以它会填满整个屏幕。
但是为什么Container
会这样决定呢?仅仅是因为这是那些创建Container
小部件的人的设计决定。它可能以不同的方式创建,您必须阅读 Container
文档以了解它的行为方式,具体取决于具体情况。
1.1.7、示例7
Center(
child: Container(
color: red,
child: Container(color: green, width: 30, height: 30)
),
)
屏幕强制Center
与屏幕大小完全相同,因此Center
会填满屏幕。
Center
告诉红色Container
它可以是任何它想要的大小,但不能大于屏幕。由于红色Container
没有大小但有一个子容器,因此它决定要与它的子容器大小相同。
红色Container
告诉它的孩子它可以是任何它想要的大小,但不能比屏幕大。
孩子是一个绿色的Container
,它想要 30 x 30
。鉴于红色Container
自身的大小与其孩子的大小相同,它也是30 x 30
红色是不可见的,因为绿色Container
完全覆盖了红色容器。
1.1.8、示例8
Center(
child: Container(
padding: const EdgeInsets.all(20.0),
color: red,
child: Container(color: green, width: 30, height: 30)
),
)
红色Container
将自身调整为它的孩子的大小,但它会考虑自己的填充。所以它也是30 x 30
加上填充。由于填充,红色课件,绿色Container
的大小与前面的示例相同。
1.1.9、示例9
ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 70,
minHeight: 70,
maxWidth: 150,
maxHeight: 150,
),
child: Container(color: red, width: 10, height: 10)
)
您可能猜想Container
必须在70到150像素之间,但您错了。ConstrainedBoox
仅对其父级接收的约束施加额外的约束。
在这里,屏幕强制ConstrainedBox
的大小与屏幕完全相同,因此它告诉其子Container
也采用屏幕的大小,从而忽略其约束参数。
1.1.10、示例10
Center(
child: ConstrainedBox(
constraints: const BoxCoonstraints(
minWidth: 70,
minHeight: 70,
maxWidth: 150,
maxHeigght: 150,
),
child: Container(color: red, width: 10, height: 10)
),
)
现在,Center
允许ConstrainedBox
大小不超过屏幕大小。ConstrainedBox
将其constraints
参数的附加约束强加到其子项。
Contrainter
必须介于70到150像素之间。它想要有10个像素,所以它最终有70个(最小值)。
1.1.11、示例11
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 70,
minHeight: 70,
maxWidth: 150,
maxHeight: 150,
),
child: Container(color: red, width: 1000, height: 1000)
),
)
Center
允许ConstrainedBox
的大小不超过屏幕大小。ConstrainedBox
参数的附加约束强加到其子项上。
Container
必须介于70到150像素之间。它想要有1000个像素,所以它最终有150个(最大值)。
1.1.12、示例12
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 70,
minHeight: 70,
maxWidth: 150,
maxHeight: 150,
),
child: Container(color: red, wiidth: 100, height: 100)
),
),
Center
允许ConstrainedBoox
的大小不超过屏幕大小。ConstrainedBox
将其constraints
参数的附加约束强加到其子项上。
Container
必须介于70到150像素之间。它想要100个像素,这就是它的大小,因为它在70到150之间。
1.1.13、示例13
UnconstrainedBox(
child: Container(color: red, width: 20, height: 50)
)
屏幕强制UnconstrainedBox
的大小与屏幕完全相同。然而,UnconstrainedBox
允许它的子Container
是它想要的任何大小。
1.1.14、示例14
UnconstrainedBox(
child: Container(color: red, width: 4000, height: 50),
)
屏幕强制UnconstrainedBoox
的大小与屏幕完全相同,而UnconstrainedBox
让它的子Container
可以是它想要的任何大小。
不幸的是,在这种情况下,Container
的宽度为4000像素并且太大而无法放入UnconstrainedBox
,因此UnconstrainedBox
会显示非常可怕的“溢出警告”。
1.1.15、示例15
OverflowBox(
minWidth: 0.0,
minHeight: 0.0,
maxWidth: double.infinity,
maxHeight: double.infinity,
child: Container(color: red, width: 4000, height: 50)
)
屏幕强制OverflowBox
的大小与屏幕完全相同,而OverflowBox
允许其子Container
为它想要的任何大小。
OverflowBox
类似于UnconstrainedBox
;不同之处在于,如果孩子不适合该空间,它不会显示任何警告。
在本例子,Container
的宽度为4000像素,太大而无法放入OverflowBox
,但OverflowBox
只是尽可能多地显示,没有给出警告。
1.1.16、示例16
UnconstrainedBox(
child: Container(color: Colors.red, width: doouble.infinity, height: 100)
)
这不会呈现任何内容,您会在控制台中看到一个错误。
UnconstrainedBox
允许其子项为任意大小,但其子项是具有无限大小的容器。
Flutter无法呈现无限大小,因为它会抛出错误并显示以下消息:BoxConstraints forces an infinite width
。
1.1.17、示例17
UnconstraintedBox(
child: LimitedBox(
maxWidth: 100,
child: Container(
color: Colors.red,
width: double.infinity,
height: 100,
),
),
)
在这里你不会再报错了,因为当LimitedBox
被UnconstrainedBox
赋予了无限大;它即将最大宽度100向下传递给它的孩子。
如果您将UnconstrainedBox
换成Center
小部件,LimitedBox
即将不再应用其限制(因为它的限制仅在它获得无限约束时才应用),并且Container
的宽度可以增长到超过100.
这解释了LimitedBox
和ConstrainedBox
之间的区别。
1.1.18、示例18
const FittedBox(
child: Text('Some Example Text.'),
)
屏幕强制FittedBox
的大小与屏幕完全相同。文本具有一些自然宽度(也称为其固有宽度),这取决于文本的数量、字体大小等。
FittedBox
允许Text
任意大小,但在Text
将其大小告知FittedBox
后,FittedBox
会缩放Text
直到它填满所有可用宽度。
1.1.19、示例19
const Center(
child: FittedBox(
child: Text('Some Example Text.'),
),
)
但是,如果将FittedBox
放在Center
小部件内会发生什么情况?Center
允许FittedBox
任意大小,直至屏幕大小。
FittedBox
然后根据文本调整自身大小,并让文本为它想要的任意大小。由于FittedBox
和Text
具有相同的大小,因此不会发生缩放。
1.1.20、示例20
const 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
位于中心小部件内,但文本太大而不适合屏幕,会发发生什么情况?
FittedBox
尝试根据文本调整自身大小,但它不能大于屏幕。然后它假定屏幕大小以使其也适合屏幕。
1.1.21、示例21
const Center(
child: Text(
'This is some very very very large text thass is too big to fit a regular screen in a single line.'
)
)
但是,如果您删除FittedBox
,则Text
会从屏幕上获取其最大的宽度,并断行以适合屏幕。
1.1.22、示例22
FittedBox(
child: Container(
height: 20.0,
width: double.infinity,
color: Colors.red,
),
)
FittedBox
只能缩放有界的小部件(具有非无限的宽度和高度)。否则,它不会渲染任何东西,你会在控制台上看到一个错误。
1.1.23、示例23
Row(
children: [
Container(color: red, child: const Text('Hello!', style: big),
Container(color: green, child: const Text('Goodbye!', style: big),
],
)
屏幕强制Row
的大小与屏幕完全相同。
就像一个UnconstrainedBox
一样,Row
不会对其子项施加任何约束,而是让它们成为他们想要的任何大小。Row
然后将它们并排放置,任何额外的空间都保持空白。
1.1.24、示例24
Row(
children: [
Container(
color: red,
child: const Text(
'This is a very long text that '
'won\'t fit the line.',
style: big,
),
),
Container(color: green, child: const Text('Goodbye!', style: big))
],
)
由于Row
不会对其子项施加任何约束,因此子项很可能太大而无法适应Row
的可用宽度,就像UnconstrainedBox
一样,Row
显示"溢出警告"。
1.1.25、示例25
Row(
children: [
Expanded(
child: Center(
child: Container(
color: red,
child: const Text(
'This is a very long text that won\'t fit the line.',
style: big,
),
),
),
),
Container(color: green, child: cont Text('Goodbye!', style: big))
],
)
当一个Row
的孩子被包裹在一个Expanded
小部件中时,Row
将不再让这个孩子定义自己的宽度。
相反,它根据其他子项定义展开后的宽度,只有这样,展开后的小部件才会强制原始子项具有展开后的宽度。
换句话说,一旦你使用Expanded
,原来的孩子的宽度就变得无关紧要,并被忽略。
1.1.26、示例26
Row(
children: [
Expanded(
child: Container(
color: red,
child: const Text(
'This is a very long text that won\'t fit the line.',
style: big,
),
),
),
Expanded(
child: Container(
color: green,
child: const Text(
'Goodbye!',
style: big,
),
),
),
],
)
如果Row
的所有子项都包含在Expanded
小部件中,则每个Expanded
的大小都与其flex
参数成比例,只有这样每个Expanded
小部件才会强制其子项具有Expanded
的宽度。
换句话说,Expanded
忽略了其子项的首选宽度。
1.1.27、示例27
Row(
children: [
Flexible(
child: Container(
color: redm
child: const Text(
'This is a very long text that won\'t fit the line.',
style: big,
),
),
),
Flexible(
child: Container(
color: green,
child: const Text(
'Goodbye!',
style: big,
),
),
),
],
)
如果您使用Flexible
而不是Expanded
,唯一的区别hiFlexible
允许其子项具有与Flexible
本身相同或更小的宽度,而Expanded
强制其子项具有与Expnanded
完全相同的宽度。但是Expanded
和Flexible
在调整自己的大小时都忽略了孩子的宽度。
注意:这意味着不可能按比例扩展`Row`子元素的大小。当您使用`Expanded`或`Flexible`时,`Row`要么使用确切的子宽度,要么完全忽略它。
1.1.28、示例28
Scaffold(
body: Container(
color: blue,
child: Column(
children: const [
Text('Hello!'),
Text('Goodbye!'),
],
),
),
)
屏幕强制Scaffold
与屏幕大小完全相同,因此Scaffold
会填满屏幕。Scaffold
告诉Container
它可以是它想要的任何大小,但不能大于屏幕。
注意:当一个小部件告诉它的孩子它可以小于某个尺寸时,我们说这个小部件向它的孩子提供松散的约束。稍后会详细介绍。
1.1.29、示例29
Scaffold(
body: SizedBox.expand(
child: Container(
color: blue,
child: Column(
children: const [
Text('Hello!'),
Text('Goodbye!),
],
),
),
),
)
如果您希望Scaffold
的孩子与Scaffold
本身的大小完全相同,您可以使用SizedBox.expand
包裹它的孩子。
注意:当一个小部件告诉它的孩子它必须有一定的大小时,我们说这个小部件向它的孩子提供了严格的约束。
2、紧约束与松约束
听到某些约束是“紧”或“松”时候很常见的,所以有必要了解这意味着什么。
严格的约束提供了一种可能性,即精确的大小。换句话说,紧约束的最大宽度等于其最小宽度,并且它的最大高度等于它的最小高度。
如果你去Flutter
的box.dart
文件并搜索BoxConstraints
构造函数,你会发现以下内容:
BoxConstraints.tight(Size size)
: minWidth = size.width,
maxWidth = size.width,
minHeight = size.height,
maxHeight = size.height;
如果您重新查看上面的示例2,它会告诉我们屏幕强制红色Container
的大小与屏幕完全相同。当然屏幕通过将严格的约束传递给Container
来做到这一点。
另一方面,松散约束和设置最大宽度和高度,但让小部件尽可能小。换句话说,松散约束的最小宽度和高度都为零:
BoxConstraints.loose(Size size)
: minWidth = 0.0,
maxWidth = size.witdh
minHeight = 0.0,
maxHeight = size.height;
如果您重新访问示例3,它会告诉我们中心让红色容器变小,但不会大于屏幕。当然,中心通过将宽松的约束传递给Container
来做到这一点。最终,Center
的真正目的是将它从其父项(屏幕)获得的严格约束转换为其子项(Container
)的松散约束。
3、学习特定小部件和布局规则
了解一般布局规则是必要的,但这还不够。
每个小部件在应用通用规则时都有很大的自由度,因此无法仅通过阅读小部件的名称来了解它会做什么。
如果你试图猜测,你很可能会猜错。除非您阅读了它的文档或研究了它的源代码,否则您无法确切地知道一个小部件的行为方式。
布局源代码通常很复杂,所以最好只阅读文档。但是,如果您决定研究布局源代码,则可以使用 IDE 的导航功能轻松找到它。
- 在您的代码中找到一个列并导航到其源代码。为此,请在
Android Studio
或IntelliJ
中使用command+B (macOS)
或control+B (Windows/Linux)
。您将被带到basic.dart
文件。由于Column
扩展了Flex
,请导航至Flex
源代码(也在basic.dart
中)。 - 向下滚动直到找到名为
createRenderObject()
的方法。如您所见,此方法返回一个RenderFlex
。这是Column
的渲染对象。现在导航到RenderFlex
的源代码,这会将您带到flex.dart
文件。 - 向下滚动,直到找到名为
performLayout()
的方法。这是为列进行布局的方法。
转载自:https://juejin.cn/post/7198476152632033335