本问现在是官方文档的一部分了

当Fluter初学者问你为什么组件里的width:100不是100像素的时候,默认的答案就是告诉他们把组件放进一个Center里,对吧?

不要这么干

如果你这么干了,他们会一次一次的问你为什么FittedBox有问题,为什么Column会overflow,又或者IntrinsicWidth是做什么的。

所以,一开始就告诉他们Flutter的布局和html有很大的不同,他们很可能就是html的高手,然后让他们记住以下的规则:

? 约束(Constraint)向下,大小(Size)向上,位置父决定

不理解这个规则,Flutter的布局是没法弄清楚的。所以,我(作者)觉得最好今早的学会它。

细节:

  • 一个组件都是从它的父组件获得约束(constraint)。一个约束就是四个double值:一个最小、最大宽度和一个最小、最大高度。
  • 然后,这个组件遍历它的子组件。一个个的通知它的子组件他们的约束(每个子组件都可能不一样),然后询问他们想要的size。
  • 然后,这个组件沿着横向的x轴和纵向的y轴排列它的子组件的位置
  • 最后,每个组件告诉它的父组件它自己在约束下的size

比如一个Column组件,已经设定了padding值,现在要给它的两个子组件设定布局:

组件 -- 询问父组件约束是啥。
父组件 -- 你只能是90300宽,3085高。
组件 -- 嗯~~ 我还要5个单位的padding,那么我的子组件只能有最大290的宽和75的高。
组件 -- 嗨,第一个子组件你必须是0 ~ 290宽,0 ~ 75高。
第一个子组件 -- 我要290宽,20高。
组件 -- 嗯~~ , 既然我要把第二个子组件放在第一个的下面,这样就剩下55的高度给第二个子组件了。
组件 -- 嗨,第二个子组件你必须是0 ~ 290宽,0 ~ 55高。
第二个子组件 -- 好的,我要140宽和30高。
组件 -- 很好,我会把第一个子组件放在x轴:5y轴:5,第二个子组件x轴:80y轴:25的位置。
组件 -- 嗨,父组件。我的size是300宽,60高。

限制(Limitation)

Flutter布局引擎在上面规则的基础上还有一些其他的限制:

  • 一个组件只可以在父组件传过来的约束的范围内确定它的大小(size)。也就是说,一般一个组件不能想多大就多大
  • 一个组件不知道,也不能决定它在屏幕上的位置。组件的位置是由它的父组件决定的。
  • 父组件的大小和位置也是由它的父组件决定的,只有在的概念下才能决定一个组件的大小和位置。

示例

下面是一个互动示例。

原文提到了CodePen,也可以在以下两种方法里选一种。
  • 使用DartPad.
  • 代码的github repo
示例 1

Container(color: Colors.red);

屏幕是Container的父组件,它会把红色的Container严丝合缝的约束在整个的屏幕内部。

所以,Container填满了整个屏幕,到处都是红色。

示例 2

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

Container想要宽100,高100,但是不行。屏幕会强制它填满屏幕。

所以Container填满了屏幕。

示例 3

屏幕强制Center填满整个屏幕,所以Center显示在全屏。

Center告诉Container可以拥有想要的大小,但是不能比屏幕还大。所以,Container的大小就是100x100。

示例 4

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

这和前一个例子并不一样,这里用的是Align而不是Center

Align也会告诉Container可以任意大小,但是如果有任意的可用空间,是不让Container居中的,它会把Contaienr放在右下角。

示例 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会告诉Container可以为任意大小,但是不能比屏幕大。因为Container没有子组件,也没有固定的大小。它会决定显示为尽可能的大,所以填充了屏幕。

但是,为什么Container要这么决定呢?这是设计决定的。所以,Container在这样的情况下会如何显示,你要查看文档。

示例 7

Center(    child: Container(      color: Colors.red,      child: Container(color: Colors.green, width: 30, height: 30),    )  )

Center会填充屏幕。

Center会告诉Container可以任意大小。Container没有大小,但是有一个子组件,所以它决定和它的子组件一样大小。

红色的Container告诉它的子组件可以为任意大小,但是不能比屏幕还大。

绿色的Container想要30 x 30。就像上面说的,红色的Container就会显示为绿色的Container的大小,也是30x30。没有红色可以显示出来,因为绿色的把红色全部覆盖住了。

示例 8

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

红色的Container会显示为其子组件的大小,但是它自己还有padding所以它本身的大小是70x70(=30x30 + 20的padding值)。最后红色因为有padding值是可见的,绿色Container和上例一样有30x30的大小。

示例 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为屏幕大小。所以它会告诉它的子组件Container显示到屏幕的大小,所以constaints参数的值都被忽略了。

示例 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之间,Container虽然设定为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允许ConstraintedBox是屏幕内的任意大小。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会对子组件施加额外的约束。

所以,Container必须是70到150之间的值。它设定的值是100,所以它就会有这个值,因为它是在70到150之间的。

示例 13

UnconstrainedBox(    child: Container(color: Colors.red, width: 20, height: 50),  )

屏幕强制UnconstrainedBox拥有和屏幕一样的大小。而UnconstrainedBox允许它的子组件有任意大小。

示例 14

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

屏幕强制UnconstrainedBox和屏幕一样大小,而UnconstrainedBox让它的Container子组件拥有任意大小。

但是,本例中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让它的子组件Container可以有任意大小。

OverflowBoxUnconstrainedBox类似,不同的地方是,如果子组件比它大的话不会包warning。

在本例中Container的宽是4000,太大了。但是OverflowBox在这里就不会像上例的UnconstrainedBox一样报警。

示例 16

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

它不会绘制出任何的东西,只会在console里报错。

UnconstrainedBox让它的子组件可以拥有任意大小,然而它的子组件的宽是double.infinity

Flutter没法绘制无限宽的大小,所以它会抛出一个错误:BoxConstraints forces an infinite width

示例 17

UnconstrainedBox(  child: LimitedBox(    maxWidth: 100,    child: Container(      color: Colors.red,      width: double.infinity,      height: 100,    )  ))
示例 18

FittedBox(    child: Text('Some Example Text.'),  )

屏幕强制FittedBox和屏幕同样大小。Text会有自己的宽度(也叫做intrinsic宽度)。这个值依赖于字体和文字的多少等。

FittedBox会让Text拥有任意的大小,但是Text把它自己的大小通知FittedBox之后,FittedBox会做缩放,直到填满整个的宽度。

示例 19

Center(    child: FittedBox(      child: Text('Some Example Text.'),    )  )

但是,如果把FittedBox放在Center里面的话会发生什么呢?Center会让FittedBox拥有任意它想要的大小。

FittedBox然后会把自己的大小缩放到Text的大小。因为FittedBoxText有同样的大小,所以就不会有缩放的发生了。

示例 20