Flutter布局约束(约束/constraint)

173 阅读12分钟

# 必读 | 深入理解 Flutter 的布局约束

  • 首先,上层 widget 向下层 widget 传递约束条件。
  • 然后,下层 widget 向上层 widget 传递大小信息。
  • 最后,上层 widget 决定下层 widget 的位置。

`

import 'package:flutter/material.dart';

class ConstraintPage extends StatelessWidget {
  const ConstraintPage({super.key});

  @override
  Widget build(BuildContext context) {
    //return style1();
    //return style2();
    //return style3();
    //return style4();
    //return style5();
    //return style6();
    //return style7();
    //return style8();
    //return style9();
    //return style10();
    //return style11();
    //return style12();
    //return style13();
    //return style14();
    //return style15();
    //return style16();
    //return style17();
    //return style18();
    //return style19();
    //return style20();
    //return style21();
    //return style22();
    //return style23();
    //return style24();
    //return style25();
    //return style26();
    //return style27();
    //return style28();
    return style29();
  }

  ///样式1
  ///整个屏幕作为 Container 的父级,并且强制 Container 变成和屏幕一样的大小。
  ///所以这个 Container 充满了整个屏幕,并绘制成红色。
  Widget style1() {
    return Container(
      color: Colors.red,
    );
  }

  ///样式2
  ///红色的 Container 想要变成 100 x 100 的大小, 但是它无法变成,因为屏幕强制它变成和屏幕一样的大小。
  ///所以 Container 充满了整个屏幕。
  Widget style2() {
    return Container(
      width: 100,
      height: 100,
      color: Colors.red,
    );
  }

  ///样式3
  ///屏幕强制 Center 变得和屏幕一样大,所以 Center 充满了屏幕。
  ///然后 Center 告诉 Container 可以变成任意大小,但是不能超出屏幕。 现在,Container 可以真正变成 100 × 100 大小了。
  Widget style3() {
    return Center(
      child: Container(
        width: 100,
        height: 100,
        color: Colors.red,
      ),
    );
  }

  ///样式4
  ///与上样例3不同的是,我们使用了 Align 而不是 Center。
  ///Align 同样也告诉 Container,你可以变成任意大小。 但是,如果还留有空白空间的话,它不会居中 Container。
  ///相反,它将会在允许的空间内,把 Container 放在右下角(bottomRight)。
  Widget style4() {
    return Align(
      alignment: Alignment.bottomRight,
      child: Container(
        width: 100,
        height: 100,
        color: Colors.red,
      ),
    );
  }

  ///样式5
  ///屏幕强制 Center 变得和屏幕一样大,所以 Center 充满屏幕。
  ///然后 Center 告诉 Container 可以变成任意大小,但是不能超出屏幕。 现在,Container 想要无限的大小,
  ///但是由于它不能比屏幕更大, 所以就仅充满屏幕。
  Widget style5() {
    return Center(
      child: Container(
        width: double.infinity,
        height: double.infinity,
        color: Colors.red,
      ),
    );
  }

  ///样式6
  ///屏幕强制 Center 变得和屏幕一样大,所以 Center 充满屏幕。
  ///然后 Center 告诉 Container 可以变成任意大小,但是不能超出屏幕。 由于 Container 没有子级而且没有固定大小,所以它决定能有多大就有多大, 所以它充满了整个屏幕。
  ///但是,为什么 Container 做出了这个决定? 非常简单,因为这个决定是由 Container widget 的创建者决定的。 可能会因创造者而异,而且你还得阅读 Container 文档 来理解不同场景下它的行为。
  Widget style6() {
    return Center(
      child: Container(
        color: Colors.red,
      ),
    );
  }

  ///样式7
  ///屏幕强制 Center 变得和屏幕一样大,所以 Center 充满屏幕。
  ///然后 Center 告诉红色的 Container 可以变成任意大小,但是不能超出屏幕。 由于 Container 没有固定大小但是有子级,所以它决定变成它 child 的大小。
  ///而它的 child 是一个想要 100 × 100 大小绿色的 Container。由于红色的 Container 和其子级一样大,所以也变为 100 × 100。由于绿色的 Container 完全覆盖了红色 Container, 所以你看不见它了。
  Widget style7() {
    return Center(
      child: Container(
        color: Colors.red,
        child: Container(
          color: Colors.green,
          width: 100,
          height: 100,
        ),
      ),
    );
  }

  ///样式8
  ///屏幕强制 Center 变得和屏幕一样大,所以 Center 充满屏幕。
  ///然后 Center 告诉红色的 Container 可以变成任意大小,但是不能超出屏幕。 由于 Container 没有固定大小但是有子级,所以它决定变成它 child 的大小。
  ///红色 Container 变为其子级的大小,但是它将其 padding 带入了约束的计算中。 所以它有一个 30 x 30 的外边距。由于这个外边距,所以现在你能看见红色了。 而绿色的 Container 则还是和之前一样。
  Widget style8() {
    return Center(
      child: Container(
        padding: const EdgeInsets.all(30),
        color: Colors.red,
        child: Container(
          color: Colors.green,
          width: 100,
          height: 100,
        ),
      ),
    );
  }

  ///样式9
  ///你可能会猜想 Container 的尺寸会在 70 到 150 像素之间,但并不是这样。 ConstrainedBox 仅对其从其父级接收到的约束下施加其他约束。
  ///在这里,屏幕迫使 ConstrainedBox 与屏幕大小完全相同, 因此它告诉其子 Widget 也以屏幕大小作为约束, 从而忽略了其 constraints 参数带来的影响。
  Widget style9() {
    return ConstrainedBox(
      constraints: const BoxConstraints(
        minWidth: 70,
        minHeight: 70,
        maxWidth: 150,
        maxHeight: 150,
      ),
      child: Container(
        color: Colors.red,
        width: 10,
        height: 10,
      ),
    );
  }

  ///样式10
  ///Center 允许 ConstrainedBox 达到屏幕可允许的任意大小。 ConstrainedBox 将 constraints 参数带来的约束附加到其子对象上。
  ///Container 必须介于 70 到 150 像素之间。虽然它希望自己有 10 个像素大小, 但最终获得了 70 个像素(最小为 70)
  Widget style10() {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          minWidth: 70,
          minHeight: 70,
          maxWidth: 150,
          maxHeight: 150,
        ),
        child: Container(
          color: Colors.red,
          width: 10,
          height: 10,
        ),
      ),
    );
  }

  ///样式11
  ///Center 允许 ConstrainedBox 达到屏幕可允许的任意大小。 ConstrainedBox 将 constraints 参数带来的约束附加到其子对象上。
  ///Container 必须介于 70 到 150 像素之间。 虽然它希望自己有 1000 个像素大小, 但最终获得了 150 个像素(最大为 150)。
  Widget style11() {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          minWidth: 70,
          minHeight: 70,
          maxWidth: 150,
          maxHeight: 150,
        ),
        child: Container(
          color: Colors.red,
          width: 1000,
          height: 1000,
        ),
      ),
    );
  }

  ///样式12
  ///Center 允许 ConstrainedBox 达到屏幕可允许的任意大小。 ConstrainedBox 将 constraints 参数带来的约束附加到其子对象上。
  ///Container 必须介于 70 到 150 像素之间。 虽然它希望自己有 100 个像素大小, 因为 100 介于 70 至 150 的范围内,所以最终获得了 100 个像素。
  Widget style12() {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          minWidth: 70,
          minHeight: 70,
          maxWidth: 150,
          maxHeight: 150,
        ),
        child: Container(
          color: Colors.red,
          width: 100,
          height: 100,
        ),
      ),
    );
  }

  ///样式13
  ///屏幕强制 UnconstrainedBox 变得和屏幕一样大,而 UnconstrainedBox 允许其子级的 Container 可以变为任意大小
  ///Container 是 20 x 50 ,所有最终获得 20 x 50 大小
  Widget style13() {
    return UnconstrainedBox(
      child: Container(
        color: Colors.red,
        width: 20,
        height: 50,
      ),
    );
  }

  ///样式14
  ///屏幕强制 UnconstrainedBox 变得和屏幕一样大, 而 UnconstrainedBox 允许其子级的 Container 可以变为任意大小。
  ///Container容器的高度为 50 像素,可以正常显示
  ///不幸的是,在这种情况下,Container容器的宽度为 4000 像素, 这实在是太大,以至于无法容纳在 UnconstrainedBox 中, 因此 UnconstrainedBox 将显示溢出警告(overflow warning)。
  Widget style14() {
    return UnconstrainedBox(
      child: Container(
        color: Colors.red,
        width: 4000,
        height: 50,
      ),
    );
  }

  ///样式15
  ///屏幕强制 OverflowBox 变得和屏幕一样大, 并且 OverflowBox 允许其子容器设置为任意大小。
  ///OverflowBox 与 UnconstrainedBox 类似,但不同的是, 如果其子级超出该空间,它将不会显示任何警告。
  ///在这种情况下,容器的宽度为 4000 像素,并且太大而无法容纳在 OverflowBox 中, 但是 OverflowBox 会全部显示,而不会发出警告。
  Widget style15() {
    return OverflowBox(
      minWidth: 0.0,
      minHeight: 0.0,
      maxWidth: double.infinity,
      maxHeight: double.infinity,
      child: Container(color: Colors.red, width: 4000, height: 50),
    );
  }

  ///样式16
  ///UnconstrainedBox 让它的子级决定成为任何大小, 但是其子级是一个具有无限大小的 Container。
  ///Flutter 无法渲染无限大的东西,所以它抛出以下错误: BoxConstraints forces an infinite width.(盒子约束强制使用了无限的宽度)
  Widget style16() {
    return UnconstrainedBox(
        child: Container(
      color: Colors.red,
      width: double.infinity,
      height: 100,
    ));
  }

  ///样式17
  ///UnconstrainedBox 让它的子级决定成为任何大小, 但是其子级是一个具有无限大小的 Container。
  ///Flutter 无法渲染无限大的东西,所以它抛出以下错误: BoxConstraints forces an infinite width.(盒子约束强制使用了无限的宽度)
  ///这次你就不会遇到报错了。 UnconstrainedBox 给 LimitedBox 一个无限的大小; 但它向其子级传递了最大为 100 的约束。
  ///如果你将 UnconstrainedBox 替换为 Center, 则LimitedBox 将不再应用其限制(因为其限制仅在获得无限约束时才适用), 并且容器的宽度允许超过 100。
  ///LimitedBox 和 ConstrainedBox 之间的区别。
  Widget style17() {
    return UnconstrainedBox(
      child: LimitedBox(
        maxWidth: 100,
        child: Container(
          color: Colors.red,
          width: double.infinity,
          height: 100,
        ),
      ),
    );

    /* return Center(
      child: LimitedBox(
        maxWidth: 100,
        child: Container(
          color: Colors.red,
          width: double.infinity,
          height: 100,
        ),
      ),
    );*/
  }

  ///样式18
  ///屏幕强制 FittedBox 变得和屏幕一样大, 而 Text 则是有一个自然宽度(也被称作 intrinsic 宽度), 它取决于文本数量,字体大小等因素。
  ///FittedBox 让 Text 可以变为任意大小。 但是在 Text 告诉 FittedBox 其大小后, FittedBox 缩放文本直到填满所有可用宽度。
  Widget style18() {
    return const FittedBox(
      child: Text('Some Example Text.'),
    );
  }

  ///样式19
  ///但如果你将 FittedBox 放进 Center widget 中会发生什么? Center 将会让 FittedBox 能够变为任意大小, 取决于屏幕大小。
  ///FittedBox 然后会根据 Text 调整自己的大小, 然后让 Text 可以变为所需的任意大小, 由于二者具有同一大小,因此不会发生缩放。
  Widget style19() {
    return const Center(
        child: FittedBox(
      child: Text('Some Example Text.'),
    ));
  }

  ///样式20
  ///然而,如果 FittedBox 位于 Center 中, 但 Text 太大而超出屏幕,会发生什么?
  ///FittedBox 会尝试根据 Text 大小调整大小, 但不能大于屏幕大小。然后假定屏幕大小, 并调整 Text 的大小以使其也适合屏幕。
  Widget style20() {
    return 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.',
        ),
      ),
    );
  }

  ///样式21
  ///然而,如果你删除了 FittedBox, Text 则会从屏幕上获取其最大宽度, 并在合适的地方换行。
  Widget style21() {
    return const Center(
      child: Text(
        'This is some very very very large text that is too big to fit a regular screen in a single line.',
      ),
    );
  }

  ///样式22
  ///FittedBox 只能在有限制的宽高中 对子 widget 进行缩放(宽度和高度不会变得无限大)。 否则,它将无法渲染任何内容,并且你会在控制台中看到错误。
  ///RenderConstrainedBox object was given an infinite size during layout.
  Widget style22() {
    return FittedBox(
      child: Container(
        height: 20.0,
        width: double.infinity,
      ),
    );
  }

  ///样式23
  ///屏幕强制 Row 变得和屏幕一样大,所以 Row 充满屏幕。
  ///和 UnconstrainedBox 一样, Row 也不会对其子代施加任何约束, 而是让它们成为所需的任意大小。 Row 然后将它们并排放置, 任何多余的空间都将保持空白。
  Widget style23() {
    return Row(
      children: [
        Container(
          color: Colors.red,
          child: const Text(
            'Hello!',
            style: TextStyle(color: Colors.black, fontSize: 12),
          ),
        ),
        Container(
          color: Colors.green,
          child: const Text('Goodbye!'),
        ),
      ],
    );
  }

  ///样式24
  ///由于 Row 不会对其子级施加任何约束, 因此它的 children 很有可能太大 而超出 Row 的可用宽度。在这种情况下, Row 会和 UnconstrainedBox 一样显示溢出警告。
  Widget style24() {
    return Row(
      children: [
        Container(
          color: Colors.red,
          child: const Text(
            'This is a very long text that won’t fit the line.',
          ),
        ),
        Container(
          color: Colors.green,
          child: const Text(
            'Goodbye!',
          ),
        ),
      ],
    );
  }

  ///样式25
  ///当 Row 的子级被包裹在了 Expanded widget 之后, Row 将不会再让其决定自身的宽度了。
  ///取而代之的是,Row 会根据所有 Expanded 的子级 来计算其该有的宽度。
  ///换句话说,一旦你使用 Expanded, 子级自身的宽度就变得无关紧要,直接会被忽略掉。
  Widget style25() {
    return Row(
      children: [
        Expanded(
          child: Container(
            color: Colors.red,
            child:
                const Text('This is a very long text that won’t fit the line.'),
          ),
        ),
        Container(
          color: Colors.green,
          child: const Text('Goodbye!'),
        ),
      ],
    );
  }

  ///样式26
  ///如果所有 Row 的子级都被包裹了 Expanded widget, 每一个 Expanded 大小都会与其 flex 因子成比例, 并且 Expanded widget 将会强制其子级具有与 Expanded 相同的宽度。
  ///换句话说,Expanded 忽略了其子 Widget 想要的宽度。
  Widget style26() {
    return Row(
      children: [
        Expanded(
          child: Container(
            color: Colors.red,
            child:
                const Text('This is a very long text that won’t fit the line.'),
          ),
        ),
        Expanded(
          child: Container(
            color: Colors.green,
            child: const Text('Goodbye!'),
          ),
        ),
      ],
    );
  }

  ///样式27
  ///如果你使用 Flexible 而不是 Expanded 的话, 唯一的区别是,Flexible 会让其子级具有与 Flexible 相同或者更小的宽度。
  ///而 Expanded 将会强制其子级具有和 Expanded 相同的宽度。 但无论是 Expanded 还是 Flexible 在它们决定子级大小时都会忽略其宽度。
  ///
  /// Row 要么使用子级的宽度, 要么使用Expanded 和 Flexible 从而忽略子级的宽度。
  Widget style27() {
    return Row(
      children: [
        Flexible(
          child: Container(
            color: Colors.red,
            child: const Text(
              'This is a very long text that won’t fit the line.',
              style: TextStyle(fontSize: 12),
            ),
          ),
        ),
        Flexible(
          child: Container(
            color: Colors.green,
            child: const Text('Goodbye!'),
          ),
        ),
      ],
    );
  }

  ///样式28
  ///屏幕强制 Scaffold 变得和屏幕一样大, 所以 Scaffold 充满屏幕。 然后 Scaffold 告诉 Container 可以变为任意大小, 但不能超出屏幕。
  ///
  /// 当一个 widget 告诉其子级可以比自身更小的话, 我们通常称这个 widget 对其子级使用 宽松约束(loose)。
  Widget style28() {
    return Scaffold(
      body: Container(
        color: Colors.blue,
        child: Column(
          children: const [
            Text('Hello!'),
            Text('Goodbye!'),
            Text(' Flexible 从而忽略子级的宽度。!'),
          ],
        ),
      ),
    );
  }

  ///样式29
  ///如果你想要 Scaffold 的子级变得和 Scaffold 本身一样大的话, 你可以将这个子级外包裹一个 SizedBox.expand。
  ///
  /// 当一个 widget 告诉它的子级必须变成某个大小的时候, 我们通常称这个 widget 对其子级使用 严格约束(tight)。
  Widget style29() {
    return Scaffold(
      body: SizedBox.expand(
        child: Container(
          color: Colors.blue,
          child: Column(
            children: const [
              Text('Hello!'),
              Text('Goodbye!'),
              Text(' Flexible 从而忽略子级的宽度。!'),
              Text(' Flexible 。!'),
            ],
          ),
        ),
      ),
    );
  }

  ///严格约束(Tight) vs 宽松约束(loose)
  ///以后你经常会听到一些约束为严格约束或宽松约束, 你花点时间来弄明白它们是值得的。
  ///严格约束给你了一种获得确切大小的选择。 换句话来说就是,它的最大/最小宽度是一致的,高度也一样。
  /// 如果你到 Flutter 的 box.dart 文件中搜索 BoxConstraints 构造器,你会发现以下内容:
  ///BoxConstraints.tight(Size size): minWidth = size.width,maxWidth = size.width,minHeight = size.height, maxHeight = size.height;
  ///如果你重新阅读 样例 2, 它告诉我们屏幕强制 Container 变得和屏幕一样大。 为何屏幕能够做到这一点, 原因就是给 Container 传递了严格约束。
  ///一个宽松约束换句话来说就是设置了最大宽度/高度, 但是让允许其子 widget 获得比它更小的任意大小。 换句话来说,宽松约束的最小宽度/高度为 0。
  ///BoxConstraints.loose(Size size): minWidth = 0.0,maxWidth = size.width,minHeight = 0.0,maxHeight = size.height;
  ///如果你访问 样例 3, 它将会告诉我们 Center 让红色的 Container 变得更小, 但是不能超出屏幕。Center 能够做到这一点的原因就在于 给 Container 的是一个宽松约束。
  ///总的来说,Center 起的作用就是从其父级(屏幕)那里获得的严格约束, 为其子级(Container)转换为宽松约束。
}

`