likes
comments
collection
share

Flutter布局之——理解Constraints

作者站长头像
站长
· 阅读数 21

当学习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轴行)。
  • 最后,小部件将自己的大小告知其父级(当然在原始限制范围内)。

例如,如果一个组合小部件爱你包含一个带有一些填充的列,并且想要按如下方式布置它的两个子部件:

Flutter布局之——理解Constraints

谈判是这样的:

  • 小部件:”嘿,家长,我的限制是什么?“
  • 家长:”你的宽度必须在80300像素之间,高度必须在3085像素之间。“
  • 小部件:”嗯,既然我想要5个像素的填充,那么我的孩子最多可以有290个像素的宽度和75个像素的高度。“
  • 小部件:“嘿,第一个孩子,你的宽度必须在0290像素之间,高度必须在075像素之间。”
  • 第一个孩子:“好的,那我希望宽290像素,高20像素。”
  • 小部件:“嗯,因为我想把我的第二个孩子放在第一个孩子下面,所以我的第二个孩子只剩下55像素的高度。”
  • 小部件:“嘿 老二,你的宽度必须在0290之间,高度必须在055之间。”
  • 第二个孩子:“好的,我希望宽140像素,高30像素。”
  • 小部件:“很好。我的第一个孩子的位置是x:5y:5,我的第二个孩子的位置是x:80y:25。”
  • 小部件:“嘿,家长,我决定我的尺寸为300像素宽,60像素高。”

1、限制

由于上面提到的布局规则,Flutter的布局引擎有几个重要的限制:

  • 小部件只能在其父级刚给定稿的约束范围内决定自己的大小。这意味着小部件通常不能有任何它想要的大小
  • 小部件无法知道也无法决定自己在屏幕的中的位置,因为是小部件的父级决定了小部件的位置。
  • 由于父级的大小和位置反过来也取决于它自己的父级,因此不在考虑整个树的情况下不可能精确定义任何小部件的大小和位置。
  • 如果一个子项想要与其父项不同的尺寸,而父项没有足够的信息来对其它,那么该子项的尺寸可能会被忽略。定义对齐方式时要具体

1.1、例子

下面针对上述有29个例子,请客官们慢慢细读,读完了就对Contraints有了进一步的了解。

1.1.1、示例1

Flutter布局之——理解Constraints

Container(color: red)

屏幕是Container的父级,它强制Container与屏幕大小完全相同。

所以Container会填满屏幕并将其涂成红色。

1.1.2、示例2

Flutter布局之——理解Constraints

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

红色的Container想要100x100,但它不能,因为屏幕强制它与屏幕大小完全相同。

所以容器填满了全屏。

1.1.3、示例3

Flutter布局之——理解Constraints

Center(
    child: Container(width: 100, height: 100, color: red),
)

屏幕强制Center与屏幕大小完全相同,因此Center会填满屏幕。

Center告诉Container它可以是它想要的任何大小,但不能大于屏幕。现在Contaiiner确实可以是100x100

1.1.4、示例4

Flutter布局之——理解Constraints

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

这与前面的示例不同,它使用Align而不是Center

Align还告诉Container它可以是它想要的任何大小,但如果有空白空间,它不会使Container居中。相反,它将Container对齐到可用空间的右下角。

1.1.5、示例5

Flutter布局之——理解Constraints

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

屏幕强制Center与屏幕大小完全相同,因此Center会填满屏幕。

Center告诉Container它可以是它想要的任何大小,但不能大于屏幕。Container想要无限大,但由于它不能大于屏幕,所以它只能填满屏幕。

1.1.6、示例6

Flutter布局之——理解Constraints

Center(
    child: Container(color: red),
)

屏幕强制Center与屏幕大小完全相同,因此Center会填满屏幕。

Center告诉Container它可以是它想要的任何大小,但不能大于屏幕。由于Container没有子元素,也没有固定的大小,它决定尽可能大,所以它会填满整个屏幕。

但是为什么Container会这样决定呢?仅仅是因为这是那些创建Container小部件的人的设计决定。它可能以不同的方式创建,您必须阅读 Container文档以了解它的行为方式,具体取决于具体情况。

1.1.7、示例7

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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

屏幕强制UnconstrainedBox的大小与屏幕完全相同。然而,UnconstrainedBox允许它的子Container是它想要的任何大小。

1.1.14、示例14

Flutter布局之——理解Constraints

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

屏幕强制UnconstrainedBoox的大小与屏幕完全相同,而UnconstrainedBox让它的子Container可以是它想要的任何大小。

不幸的是,在这种情况下,Container的宽度为4000像素并且太大而无法放入UnconstrainedBox,因此UnconstrainedBox会显示非常可怕的“溢出警告”。

1.1.15、示例15

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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

这不会呈现任何内容,您会在控制台中看到一个错误。

UnconstrainedBox允许其子项为任意大小,但其子项是具有无限大小的容器。

Flutter无法呈现无限大小,因为它会抛出错误并显示以下消息:BoxConstraints forces an infinite width

1.1.17、示例17

Flutter布局之——理解Constraints

UnconstraintedBox(
    child: LimitedBox(
        maxWidth: 100,
        child: Container(
            color: Colors.red,
            width: double.infinity,
            height: 100,
        ),
    ),
)

在这里你不会再报错了,因为当LimitedBoxUnconstrainedBox赋予了无限大;它即将最大宽度100向下传递给它的孩子。

如果您将UnconstrainedBox换成Center小部件,LimitedBox即将不再应用其限制(因为它的限制仅在它获得无限约束时才应用),并且Container的宽度可以增长到超过100.

这解释了LimitedBoxConstrainedBox之间的区别。

1.1.18、示例18

Flutter布局之——理解Constraints

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

屏幕强制FittedBox的大小与屏幕完全相同。文本具有一些自然宽度(也称为其固有宽度),这取决于文本的数量、字体大小等。

FittedBox允许Text任意大小,但在Text将其大小告知FittedBox后,FittedBox会缩放Text直到它填满所有可用宽度。

1.1.19、示例19

Flutter布局之——理解Constraints

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

但是,如果将FittedBox放在Center小部件内会发生什么情况?Center允许FittedBox任意大小,直至屏幕大小。

FittedBox然后根据文本调整自身大小,并让文本为它想要的任意大小。由于FittedBoxText具有相同的大小,因此不会发生缩放。

1.1.20、示例20

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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

FittedBox只能缩放有界的小部件(具有非无限的宽度和高度)。否则,它不会渲染任何东西,你会在控制台上看到一个错误。

1.1.23、示例23

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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

Flutter布局之——理解Constraints

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完全相同的宽度。但是ExpandedFlexible在调整自己的大小时都忽略了孩子的宽度。

注意:这意味着不可能按比例扩展`Row`子元素的大小。当您使用`Expanded``Flexible`时,`Row`要么使用确切的子宽度,要么完全忽略它。

1.1.28、示例28

Flutter布局之——理解Constraints

Scaffold(
    body: Container(
        color: blue,
        child: Column(
            children: const [
                Text('Hello!'),
                Text('Goodbye!'),
            ],
        ),
    ),
)

屏幕强制Scaffold与屏幕大小完全相同,因此Scaffold会填满屏幕。Scaffold告诉Container它可以是它想要的任何大小,但不能大于屏幕。

注意:当一个小部件告诉它的孩子它可以小于某个尺寸时,我们说这个小部件向它的孩子提供松散的约束。稍后会详细介绍。

1.1.29、示例29

Flutter布局之——理解Constraints

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

如果您希望Scaffold的孩子与Scaffold本身的大小完全相同,您可以使用SizedBox.expand包裹它的孩子。

注意:当一个小部件告诉它的孩子它必须有一定的大小时,我们说这个小部件向它的孩子提供了严格的约束。

2、紧约束与松约束

听到某些约束是“紧”或“松”时候很常见的,所以有必要了解这意味着什么。

严格的约束提供了一种可能性,即精确的大小。换句话说,紧约束的最大宽度等于其最小宽度,并且它的最大高度等于它的最小高度。

如果你去Flutterbox.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 StudioIntelliJ 中使用 command+B (macOS)control+B (Windows/Linux)。您将被带到 basic.dart 文件。由于 Column 扩展了 Flex,请导航至 Flex 源代码(也在 basic.dart 中)。
  • 向下滚动直到找到名为 createRenderObject() 的方法。如您所见,此方法返回一个 RenderFlex。这是 Column 的渲染对象。现在导航到 RenderFlex 的源代码,这会将您带到 flex.dart 文件。
  • 向下滚动,直到找到名为 performLayout() 的方法。这是为列进行布局的方法。

Flutter布局之——理解Constraints