Flutter - 深刻布局规则

本问如今是官方文档的一部分了

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

不要这么干git

若是你这么干了,他们会一次一次的问你为何FittedBox有问题,为何Column会overflow,又或者IntrinsicWidth是作什么的。github

因此,一开始就告诉他们Flutter的布局和html有很大的不一样,他们极可能就是html的高手,而后让他们记住如下的规则:函数

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

不理解这个规则,Flutter的布局是无法弄清楚的。因此,我(做者)以为最好今早的学会它。布局

细节:学习

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

好比一个Column组件,已经设定了padding值,如今要给它的两个子组件设定布局:
1_757yPUwLSMwxOSMPUJd7dw.png字体

组件 -- 询问父组件约束是啥。
父组件 -- 你只能是 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布局引擎在上面规则的基础上还有一些其余的限制:flex

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

示例

下面是一个互动示例。spa

原文提到了CodePen,也能够在如下两种方法里选一种。
示例 1

1_6yLUHp92rQtZDSEv9aBQcA.png

Container(color: Colors.red);

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

因此,Container填满了整个屏幕,处处都是红色。

示例 2

1_6yLUHp92rQtZDSEv9aBQcA.png

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

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

因此Container填满了屏幕。

示例 3

1_Mwp8fmF4Uce1G6pxuuJNBw.png

屏幕强制Center填满整个屏幕,因此Center显示在全屏。

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

示例 4

1_GuTTQKTH8LCB1Ha343NbWQ.png

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

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

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

示例 5

1_zt-a_hMkHJpLvYGmFdcFsg.png

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

屏幕强制Center填充整个屏幕,因此Center填满了屏幕。

Center告诉Container能够是任意大小,Container要的是无限大,可是它又不能比屏幕还大,因此也填满了屏幕。

示例 6

1_FPIri__CN8Gq1DvEobZopg.png

Center(child: Container(color: Colors.red))

屏幕仍是会强制Center填充屏幕。

Center会告诉Container能够为任意大小,可是不能比屏幕大。由于Container没有子组件,也没有固定的大小。它会决定显示为尽量的大,因此填充了屏幕。

可是,为何Container要这么决定呢?这是设计决定的。因此,Container在这样的状况下会如何显示,你要查看文档。

示例 7

1_AYOkoZFkYhmmmmQMo3hrRw.png

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

1_c3fwXjxtfHl34QyG1MU2ng.png

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

1_XFr3RxUmfBx0uzP62D5fxA.png

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

1_VjsbcsI4VM8uU-H-5_UVhw.png

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

1_aZuAYE68PZuUeUBmtI2dNw.png

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

1_CCHJKL7Q9J_bGtX7CU5CLA.png

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

1_brAZzN2-S_fDGXiMDUZheQ.png

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

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

示例 14

1_OWv20n8bQInTHZAP1CxMHA.png

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

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

可是,本例中Container设定的是4000的宽,这样太大了无法放进UnconstrainedBox,因此UnconstrainedBox会显示出“overflow warning”。

示例 15

1_9JfJofqdzHvOov1vl4NA4A.png

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

1_mpooMmLzFAQfKkQpuF_T_g.png

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

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

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

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

示例 17

1_UmwN0tqr7iDvsnNLr0tjtg.png

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

1_zu9CkTZLLcFEzErsxMVwzA.png

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

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

FittedBox会让Text拥有任意的大小,可是Text把它本身的大小通知FittedBox以后,FittedBox会作缩放,直到填满整个的宽度。

示例 19

1_VBIPl_EXOQVCx7LBCBsXDg.png

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

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

FittedBox而后会把本身的大小缩放到Text的大小。由于FittedBoxText有一样的大小,因此就不会有缩放的发生了。

示例 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里,并且文字内容多到屏幕放不下的时候会发生什么呢?

FittedBox会缩放到适应Text的大小,可是它不可能比屏幕还大。它会首先占用屏幕的大小,而后对Text缩放到能够显示在屏幕里。

示例 21

ex 21.png

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

ext22.png

FittedBox(  
  child: Container(  
    height: 20.0,  
    width: double.infinity,  
  )  
)

注意FittedBox只能缩放一个有边界的组件(宽度或者高度都没有无限值)。不然它不会绘任何的东西,只会在console里显示一条报错信息。

示例 23

23.png

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

屏幕会强制Row使用屏幕的宽度。

就和UnconstrainedBox同样,Row也不会对它的子组件施加任何的约束,而是容许他们有他们想要的任意大小。Row会把他们挨个放好,而后空出剩余的空间。

示例 24

24.png

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同样显示错误信息“overflow warning”。

示例 25

25.png

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包裹的话,那么这个组件的宽度就不会再起做用了。它会使用Expanded的宽度。Expanded则强制原先的子组件使用Expanded的宽度。

总之一句话,只要你用了Expanded,那么它子组件的宽度就无效了。

示例 26

26.png

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包裹,那么每一个子组件所占的比例是由flex参数决定的。每一个子组件租后都会接受Expanded的宽度。

示例 27

27.png

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代替了ExpandedFlexible会让它的子组件有更小的宽度吗?

FlexibleExpanded都会忽略子组件的大小。

也就是说在Row里不可能根据子组件的大小按比例缩放子组件。Row要么使用子组件的宽,要么在你使用了Expanded或者Flexible的时候彻底忽略他们的宽度。

示例 28

1_V3mGIoK_py3zWf_eZkKxzg.png

屏幕强制Scaffold显示到屏幕的大小。因此,Scaffold填充了屏幕。

Scaffold告诉Container它可使屏幕内的任意大小。

注意:当一个组件告诉它的子组件比某个特定的值小,咱们能够认为这个组件给它的子组件提供了“松散”的约束。后面会有更详细讲解。

示例 29

1_X_eWxGnCsvIkXlFBtLskyg.png

Scaffold(  
  body: **SizedBox.expand**(  
    child: Container(  
      color: blue,  
      child: Column(  
        children: \[  
          Text('Hello!'),  
          Text('Goodbye!'),  
      ],  
))))

若是咱们要Scaffold的子组件和它有相同的大小。咱们能够把它的子组件放到SizedBox.expand

注意:当一个组件告诉它的子组件必须是某个特定的值,咱们能够说这个组件给它的子组件提供了“紧”约束。

紧的和松散的约束

常常会听到一种说法“紧”或者“松散”的约束,那么他们到底指的是什么呢?

一个约束只提供了一种可能,一个准确的值。也就是说,一个紧约束,它的最大宽和最小宽是同样的,最大高和最小高也是同样的。

若是你到Flutter的box.dart文件查找BoxConstraints构造函数,你会发现:

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

若是你再看示例 2,屏幕强制红色的Container显示到屏幕的大小。屏幕能够这么作就是给Container传入了约束。

一个松散的约束是指给子组件设定最大宽和高,可是让子组件尽量的小。也就是说,一个松散约束有最小的宽和高,他们都等于0.

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

示例 3中,Center让红色的Container不要比屏幕还大。Center就是给Container传入了一个松散的约束。最后的效果是Center收到了父组件传给它的紧约束,而给它的子组件Container传入了松散约束。

从特定的组件学习布局规则

知道了基本的布局规则,也仍是不够的。

每一个组件都有高度的自由决定如何使用基本布局规则,因此若是只是看到了组件名称是没法预测这个组件的布局行为的。

若是你去猜,基本都会错。你不读文档或者组件的源代码是没法准确知道一个组件如何布局的。

源代码基本都很复杂,因此最好仍是看文档。固然若是你想学习布局的相关代码,IDE的支持是足够支持你的想法的。

这里有个例子:

  • 找到Column,而后跳转到它的源码。最终你会到basic.dart文件。Column继承自Flex,还能够跳转到Flex,它也在basic.dart文件里。
  • 往下找到一个叫作createRenderObject的方法。这个方法返回RenderFlex。这就是Column对应的绘制对象。再跳转到RenderFlex的源码,你就会到flex.dart文件。
  • 往下找到~performLayout的方法,这个方法就是为Column`布局的方法。

1_t3WEi2V6BR3ydRIbacDK8w.png

很是感谢Simon Lightfoot的校验,文章开头的图片和对文章内容的建议。

相关文章
相关标签/搜索