理解 Flutter 的布局

2021/4/18 posted in  Flutter

布局开发中的规则

  1. 上层 widget 向下层 widget 传递约束条件
  2. 下层 widget 向上层 widget 传递大小信息
  3. 上层 widget 决定下层 widget 的位置

Flutter 布局引擎的限制

  • 一个 widget 仅在其父级给其约束的情况下才能决定自身的大小。这意味着 widget 通常情况下 不能任意获得其想要的大小。

  • 一个 widget 无法知道,也不需要决定其在屏幕中的位置。因为它的位置是由其父级决定的。

  • 当轮到父级决定其大小和位置的时候,同样的也取决于它自身的父级。所以,在不考虑整棵树的情况下,几乎不可能精确定义任何 widget 的大小和位置。

  • 如果子级想要拥有和父级不同的大小,然而父级没有足够的空间对其进行布局的话,子级的设置的大小可能会不生效。 这时请明确指定它的对齐方式


约束冲突情况

ConstrainedBox(
  constraints: BoxConstraints(
      minWidth: 70, minHeight: 70, maxWidth: 150, maxHeight: 150),
  child: Container(color: red, width: 10, height: 10),
)

这里的约束代码,因为ConstrainedBox 被作为父类直接展示,其尺寸被迫使使用屏幕大小,其 constraints 就失去了作用。因此其子 Widget 也就没有接收到相应的约束,仍旧按照屏幕大小展示。效果:

想要上面布局中的 Container 接收到父Widget 的约束,可以将布局代码修改如下:

Center(
  child: ConstrainedBox(
    constraints: BoxConstraints(
        minWidth: 70, minHeight: 70, maxWidth: 150, maxHeight: 150),
    child: Container(color: red, width: 10, height: 10),
  ),
)

解除约束

使用 UnconstrainedBox 将让布局充满可能性(意外),它不会对子级施加任何约束。

UnconstrainedBox 允许其子级的 Container 可以变为任意大小

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

Row

Row 也不会对其子级施加任何约束,所以它也会遇到溢出的警告。

所以可以将 Row 的子级包裹 Expanded widget,每一个 Expanded 大小都会与其 flex 因子成比例,并且 Expanded widget 将会强制其子级具有与 Expanded 相同的宽度。

Row(
  children: [
    Expanded(
      child: Container(
        color: red,
        child: Text(
          'This is a very long text that won\'t fit the line.',
          style: big,
        ),
      ),
    ),
    Expanded(
      child: Container(
        color: green,
        child: Text(
          'Goodbye!',
          style: big,
        ),
      ),
    ),
  ],
)

换句话说,Expanded 忽略了其子 Widget 想要的宽度

也可以使用 Flexible 来忽略子级的大小,它跟 Expanded 唯一的区别是 Flexible 会让其子级具有与 Flexible 相同或者更小的宽度。而 Expanded 将会强制其子级具有和 Expanded 相同的宽度

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


严格约束(Tight) vs 宽松约束(loose)

当一个 widget 告诉其子级可以比自身更小的话,我们通常称这个 widget 对其子级使用 宽松约束(loose)。

当一个 widget 告诉它的子级必须变成某个大小的时候,我们通常称这个 widget 对其子级使用 严格约束(tight)。

如果你想要 Scaffold 的子级变得和 Scaffold 本身一样大的话,你可以将这个子级外包裹一个 SizedBox.expand。

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

Reference

布局约束