Flutter你竟是这样的布局

对于Flutter学习者来讲,掌握Flutter的布局行为,直接决定了开发者在布局的时候是否能作到高效、快速的开发,可是初学者面对茫茫多的Widget以及各类没法预料的布局行为,老是很难将心中所想,转化为Flutter的代码。git

本文翻译整理自https://flutter.dev/docs/development/ui/layout/constraints


顺便插句话,个人开源项目Flutter_dojo,刚发布了2.0,欢迎你们体验。 Flutter_dojo github

在这里插入图片描述
欢迎你们体验。


当学习Flutter的人问你,为何宽度为100的某些小部件在显示的时候,宽度不为100像素时,你的默认答案是告诉他们将小部件放在Center内,对吗?算法

不要这样作。若是这样作,他们会一次又一次地回来,询问为何某些FittedBox不起做用,为何Column溢出了,或者IntrinsicWidth应该作什么。bash

相反,请先告诉他们Flutter布局与HTML布局(多是他们很是熟悉的)有很大不一样,而后让他们记住如下规则:函数

Constraints go down. Sizes go up. Parent sets position。布局

若是不了解此规则,就没法真正理解Flutter的布局,所以Flutter开发人员应尽早学习。学习

更详细地: Widget从其父级得到本身的约束。约束只是一组4个双精度数:字体

  • 最小和最大宽度
  • 最小和最大高度

而后Widget遍历它的全部子Widget。Widget一个接一个地告诉其孩子约束(每一个孩子可能有所不一样),而后询问每一个孩子想要的大小,而后,Widget将其孩子定位(水平地在x轴上布局,垂直地在y轴上布局),最后,该小部件将其自身的大小告诉父级(固然,在原始约束内)。flex

例如,若是一个组合Widget包含带有一些Padding和Column,而且但愿如图所示布置其两个Widget:ui

谈判是这样的:

  • Widget:嗨,Parent,个人约束是什么?
  • Parent Widget:你的宽度必须在80到300像素之间,而高度必须在30到85像素之间。
  • Widget:嗯,由于我要有5像素的Padding,因此个人子Widget最多能够有290像素的宽度和75像素的高度。
  • Widget:嗨,第一个子Widget,你的宽度必须在0到290像素之间,而且必须在0到75高之间。
  • First child:好,那我但愿宽290像素,高20像素。
  • Widget:嗯,因为我想将第二个子Widget放到第一个子Widget下面,因此第二个子Widget只剩下55像素的高度。
  • Widget:嗨,第二个子Widget,你的高度必须在0到290之间,而且必须在0到55高之间。
  • Second child:好吧,我但愿宽140像素,高30像素。
  • Widget:很好。个人第一个孩子的位置x:5和y:5,第二个孩子的位置x:80和y:25。
  • Widget:亲爱的父母,我决定将尺寸设为300像素宽,60像素高。

Limitations

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

  • Widget只能在其父级赋予的限制内决定其自身大小。这意味着Widget一般不能具备所需的任何大小。布局是自上而下,当前widget会有基本的一些约束(来自它的父元素),主要是关于宽高的最小值和最大值
  • Widget没法知道也不决定其在屏幕上的位置,由于Widget的父级决定小部件的位置。它会依次询问子元素关于布局的基本限制要求,让子元素上报指望的布局结果,而后根据现状和本身布局算法的特色,告诉子元素应该放到那儿,占多大空间

因为父级的大小和位置又取决于其父级,所以在不考虑整个树的状况下就没法精肯定义任何小部件的大小和位置。

每一个widget不必定会获得它指望的布局大小,这方面显著的例子是ConstrainedBox,很容易让人困惑。 每一个widget不能决定在屏幕中的位置,由父元素决定 由于这种布局逻辑须要层层考虑上层元素,因此一个元素的最终布局须要考虑整个UI里widget树。 若是为了精确局部布局,Container和ConstrainedBox会是一个可行的修饰布局。

Examples

下面的29个示例,将演示Flutter的布局思想。

github.com/marcglasber…

Example 1

在这里插入图片描述

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

屏幕是Container的父级,它强制容器与屏幕的尺寸彻底相同。 所以,容器将屏幕填满并涂成红色。

Example 2

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

想要红色的容器为100×100,但不是,由于屏幕会强制使其尺寸与屏幕彻底相同。 所以,容器充满了屏幕。

Example 3

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

屏幕会强制Center与屏幕彻底相同,所以Center会填满整个屏幕。 Center告诉Container它能够是所需的任何大小,但不能大于屏幕大小。 因此如今容器确实能够是100×100。

Example 4

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

这与上一个示例不一样,由于它使用Align而不是Center。 Align一样告诉Container它能够是任何所需的大小,同时会在剩余的可用空间中bottom-right对齐。

Example 5

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

屏幕会强制Center与屏幕彻底相同,所以Center会填满整个屏幕。 Center告诉Container它能够是所需的任何大小,但不能大于屏幕大小。 容器但愿具备无限大小,但因为不能大于屏幕,所以只能填充屏幕。

Example 6

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

屏幕会强制Center与屏幕彻底相同,所以Center会填满整个屏幕。 Center告诉Container它能够是所需的任何大小,但不能大于屏幕大小。 因为该Container没有Child且没有固定的大小,所以它决定要尽量大,所以将其填满整个屏幕。 可是Container为何要这样决定呢?仅仅是由于这是建立Container的人的设计决定。 其它的Widget的建立方式可能有所不一样,具体取决于状况。

Example 7

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

屏幕会强制Center与屏幕彻底相同,所以Center会填满整个屏幕。 Center告诉红色Container它能够是所需的任何大小,但不大于屏幕。 因为红色的Container没有大小,可是有一个Child,所以它决定要与孩子的大小相同。 红色的Container告诉其子项能够是它想要的任何大小,但不能大于屏幕大小。 这个Child是一个绿色的Container,它但愿是30×30。考虑到红色Container的大小与其孩子的大小相同,它也是30×30,因此红色是不可见的,由于绿色的Container会彻底覆盖红色Container。

Example 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×30加上padding。 因为有padding,所以能够看到红色,绿色Container与上一个示例中的大小相同。

Example 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也假定屏幕大小,从而忽略了其约束参数。

Example 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未来自其约束参数的附加约束施加到其子对象上。 Container必须介于70到150像素之间。 它但愿有10个像素,因此最终有70个像素(最小)。

Example 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未来自其约束参数的附加约束施加到其子对象上。 Container必须介于70到150像素之间。 它但愿有1000个像素,因此最终有150个(最大)。

Example 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之间。

Example 13

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

屏幕强制UnconstrainedBox与屏幕大小彻底相同。 可是,UnconstrainedBox容许其子Container设置任意大小。

Example 14

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

屏幕强制UnconstrainedBox与屏幕大小彻底相同,UnconstrainedBox将其子Container设为任意大小。 不幸的是,在这种状况下,容器的宽度为4000像素,太大而没法容纳在UnconstrainedBox中,所以UnconstrainedBox显示溢出警告。

Example 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容许其子容器设置为任意大小。 OverflowBox与UnconstrainedBox相似,但不一样的是,若是Child不适合该空间,它将不会显示任何警告。 在这种状况下,容器的宽度为4000像素,而且太大而没法容纳在OverflowBox中,可是OverflowBox会尽量地显示尽量多的内容,而不会发出警告。

Example 16

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

你会在控制台中看到错误。 UnconstrainedBox可让它的子Widget具备所需的任何大小,可是其子Widget是一个具备无限大小的Container。 Flutter没法呈现无限大小,所以会出现如下错误消息:BoxConstraints forces an infinite width.

Example 17

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

这样就不会再出现错误,由于当UnconstrainedBox为LimitedBox赋予无限大小时,它向下传递的约束为最大宽度是100像素。 若是你将UnconstrainedBox替换为Center,则LimitedBox将再也不应用其限制(由于其限制仅在得到无限约束时才适用),而且容器的宽度容许超过100。 这解释了LimitedBox和ConstrainedBox之间的区别。

Example 18

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

屏幕将强制FittedBox与屏幕彻底相同。 文本将根据宽度调整自有的宽度属性,字体属性等。 FittedBox容许文本的尺寸为任意大小,但在将文本告知FittedBox大小后,FittedBox缩放文本直到填满全部可用宽度。

Example 19

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

可是,若是将FittedBox放在Center内会怎样?Center会将FittedBox设置为所需的任何大小,直至屏幕大小。 而后,将FittedBox调整为Text大小,并让Text为所需的任何大小。 因为FittedBox和Text具备相同的大小,所以不会发生缩放。

Example 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会尝试根据文本大小调整大小,但不能大于屏幕大小。而后假定屏幕大小,并调整文本的大小以使其也适合屏幕。

Example 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从屏幕上获取其最大宽度,并在合适 的地方换行。

Example 22

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

FittedBox只能在有限制的宽高中进行Child的缩放(宽度和高度非无限大)。 不然,它将没法呈现任何内容,而且你会在控制台中看到错误。

Example 23

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

屏幕强制行与屏幕大小彻底相同。 就像UnconstrainedBox同样,Row不会对其子代施加任何约束,而是让它们成为所需的任意大小。Row而后将它们并排放置,任何多余的空间都将保持空白。

Example 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不会对其子级施加任何约束,所以子Widget颇有可能太大而没法容纳Row的可用宽度。 在这种状况下,就像UnconstrainedBox同样,Row会显示溢出警告。

Example 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的子Child被包裹在Expanded中时,Row将再也不让该Child定义本身的宽度。 取而代之的是,Row会根据全部Expanded的Child来计算其该有的宽度。 换句话说,一旦您使用Expanded,原始Widget的宽度就变得可有可无,而且会被忽略。

Example 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的子Widget都包装在Expeded中,则每一个Expeded的大小均与其flex参数成比例,子Child会设置为计算的Expanded宽度。 换句话说,Expanded忽略了其子Widget宽度。

Example 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使其子元素的宽度等于或小于其自身的宽度,而Expanded强制其子元素具备与Expeded彻底相同的宽度。 可是,在调整尺寸时,Expanded和Flexible的都忽略了孩子的宽度。

注意:这意味着,Row要么使用子Child的宽度,要么使用Expanded和Flexible从而忽略Child的宽度。

Example 28

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

屏幕会强制设置Scaffold与屏幕大小彻底相同,所以Scaffold会填满屏幕。 Scaffold告诉容器它能够是所需的任何大小,但不能大于屏幕大小。

注意:当Widget告诉其子Widget它能够小于特定大小时,咱们说该Widget为其Child提供了loose约束。

Example 29

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

若是你但愿Scaffold的子Widget与本身的Scaffold大小彻底相同,则可使用SizedBox.expand包装其Child。

注意:当小部件告诉其子级必须具备必定大小时,咱们说该小部件为其子级提供了tight约束。

Tight vs loose constraints

前面常常提到一些约束是tight或loose,因此你值得知道这是什么意思。

tight constraint提供了一种可能性,即确切的大小。换句话说,tight constraint的最大宽度等于其最小宽度。 而且其最大高度等于其最小高度。

若是转到Flutter的box.dart文件并搜索BoxConstraints构造函数,则会发现如下内容:

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

若是你从新查看上面的示例2,它将告诉咱们屏幕强制红色Container与屏幕彻底相同。 固然,屏幕是经过将tight constraint传递给Container来实现的。

另外一方面,宽松的约束设置了最大宽度和高度,但使小部件尽量小。 换句话说,宽松约束的最小宽度和高度都等于零:

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

若是您从新查看示例3,它将告诉咱们Center使红色Container变得更小,但不大于屏幕。 Center经过向Container传递loose constraint来作到这一点。 最终,Center的主要目的是将其从父级(屏幕)得到的tight constraint转换为对其子级(容器)的loose constraint。

Learning the layout rules for specific widgets

知道通常的布局规则是必要的,但这还不够。 每一个Widget在应用通常规则时都有很大的自由度,所以没法仅经过读取Widget的名称就知道可能会作什么。 若是你尝试猜想,可能会猜错。除非你已阅读Widget的文档或研究了其源代码,不然你没法确切知道Widget的行为。 布局源代码一般很复杂,所以最好阅读文档。 可是,若是你决定研究布局源代码,则可使用IDE的导航功能轻松找到它。

下面是一个例子:

在你的代码中找到一个Column并导航至其源代码。为此,请在Android Studio或IntelliJ中使用command + B(macOS)或control + B(Windows / Linux)。 你将被带到basic.dart文件。因为Column扩展了Flex,请导航至Flex源代码(也位于basic.dart中)。 向下滚动直到找到一个名为createRenderObject()的方法。 如你所见,此方法返回一个RenderFlex。这是Column的渲染对象。如今导航到RenderFlex的源代码,将您带到flex.dart文件。 向下滚动,直到找到一个名为performLayout()的方法。 这是执行列布局的方法。

对Flutter感兴趣的朋友能够加入个人Flutter修仙群。

在这里插入图片描述
相关文章
相关标签/搜索