跳到主内容

理解约束

Flutter 的小部件约束、尺寸、定位以及它们之间的交互方式。

Hero image from the article

当有人学习 Flutter 时,如果他们问你为什么带有 width: 100 的某个小部件不是 100 像素宽,默认的答案是告诉他们将该小部件放在一个 Center 中,对吧?

不要这样做。

如果您这样做,他们会一次又一次地回来问你,为什么某个 FittedBox 没有起作用,为什么那个 Column 溢出了,或者 IntrinsicWidth 应该做什么。

相反,首先告诉他们 Flutter 布局与 HTML 布局非常不同(这可能是他们之前接触过的),然后让他们记住以下规则

约束向下传递。尺寸向上确定。父组件设置位置。

在不了解这条规则的情况下,无法真正理解 Flutter 布局,因此 Flutter 开发者应该尽早学习它。

更详细地说

  • 一个组件从其 父组件 获得自己的 约束。一个约束只是一组 4 个双精度数:最小和最大宽度,以及最小和最大高度。
  • 然后,该组件遍历其自己的 子组件 列表。逐个地,该组件告诉其子组件它们的 约束(对于每个子组件可能不同),然后询问每个子组件想要的大小。
  • 然后,该组件定位其 子组件(在 x 轴上水平,在 y 轴上垂直),逐个地。
  • 最后,该组件将其自身的 尺寸 告诉其父组件(当然,在原始约束范围内)。

例如,如果一个组合组件包含带有填充的列,并希望按以下方式布局其两个子组件

Visual layout

协商过程如下

组件:“嘿,父组件,我的约束是什么?”

父组件:“你必须是 0300 像素宽,以及 085 高。”

组件:“嗯,由于我想要 5 像素的填充,那么我的子组件最多可以有 290 像素的宽度和 75 像素的高度。”

组件:“嘿,第一个子组件,你必须是 0290 像素宽,以及 075 高。”

第一个子组件:“好的,那么我希望是 290 像素宽,以及 20 像素高。”

组件:“嗯,由于我想把我的第二个子组件放在第一个子组件下面,这只留下了 55 像素的高度给我的第二个子组件。”

组件:“嘿,第二个子组件,你必须是 0290 宽,以及 055 高。”

第二个子组件:“好的,我希望是 140 像素宽,以及 30 像素高。”

组件:“很好。我的第一个子组件的位置是 x: 5y: 5,我的第二个子组件的位置是 x: 80y: 25。”

组件:“嘿,父组件,我决定我的尺寸是 300 像素宽,以及 60 像素高。”

局限性

#

Flutter 的布局引擎被设计成一个单次过程。这意味着 Flutter 非常高效地布局其小部件,但也导致了一些限制

  • 一个组件只能在其父组件给定的约束范围内确定自己的大小。这意味着一个组件通常不能拥有任何它想要的大小

  • 一个组件不能知道并且不决定它在屏幕上的位置,因为是组件的父组件决定了组件的位置。

  • 由于父组件的大小和位置也取决于它自己的父组件,因此在不考虑整个树的情况下,无法精确定义任何组件的大小和位置。

  • 如果子组件想要的大小与其父组件不同,并且父组件没有足够的信息来对齐它,那么子组件的大小可能会被忽略。在定义对齐方式时要具体。

在 Flutter 中,小部件由其底层的 RenderBox 对象渲染。Flutter 中的许多盒子,特别是那些只接受单个子组件的盒子,会将它们的约束传递给它们的子组件。

通常,根据它们如何处理约束,有三种类型的盒子

  • 那些试图尽可能大的盒子。例如,CenterListView 使用的盒子。
  • 那些试图与它们的子组件大小相同的盒子。例如,TransformOpacity 使用的盒子。
  • 那些试图达到特定大小的盒子。例如,ImageText 使用的盒子。

有些小部件,例如 Container,根据其构造函数参数的不同而有所不同。 Container 构造函数默认情况下会尝试尽可能大,但如果您为其提供 width,例如,它会尝试遵守该宽度并达到该特定大小。

其他的,例如 RowColumn(弹性盒子),根据它们收到的约束而变化,如 弹性 部分所述。

示例

#

为了获得交互式体验,请使用以下 DartPad。使用编号的水平滚动条在 29 个不同的示例之间切换。

import 'package:flutter/material.dart';

void main() => runApp(const HomePage());

const red = Colors.red;
const green = Colors.green;
const blue = Colors.blue;
const big = TextStyle(fontSize: 30);

//////////////////////////////////////////////////

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

  @override
  Widget build(BuildContext context) {
    return const FlutterLayoutArticle([
      Example1(),
      Example2(),
      Example3(),
      Example4(),
      Example5(),
      Example6(),
      Example7(),
      Example8(),
      Example9(),
      Example10(),
      Example11(),
      Example12(),
      Example13(),
      Example14(),
      Example15(),
      Example16(),
      Example17(),
      Example18(),
      Example19(),
      Example20(),
      Example21(),
      Example22(),
      Example23(),
      Example24(),
      Example25(),
      Example26(),
      Example27(),
      Example28(),
      Example29(),
    ]);
  }
}

//////////////////////////////////////////////////

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

  String get code;

  String get explanation;
}

//////////////////////////////////////////////////

class FlutterLayoutArticle extends StatefulWidget {
  const FlutterLayoutArticle(this.examples, {super.key});

  final List<Example> examples;

  @override
  State<FlutterLayoutArticle> createState() => _FlutterLayoutArticleState();
}

//////////////////////////////////////////////////

class _FlutterLayoutArticleState extends State<FlutterLayoutArticle> {
  late int count;
  late Widget example;
  late String code;
  late String explanation;

  @override
  void initState() {
    count = 1;
    code = const Example1().code;
    explanation = const Example1().explanation;

    super.initState();
  }

  @override
  void didUpdateWidget(FlutterLayoutArticle oldWidget) {
    super.didUpdateWidget(oldWidget);
    var example = widget.examples[count - 1];
    code = example.code;
    explanation = example.explanation;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Layout Article',
      home: SafeArea(
        child: Material(
          color: Colors.black,
          child: FittedBox(
            child: Container(
              width: 400,
              height: 670,
              color: const Color(0xFFCCCCCC),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Expanded(
                    child: ConstrainedBox(
                      constraints: const BoxConstraints.tightFor(
                        width: double.infinity,
                        height: double.infinity,
                      ),
                      child: widget.examples[count - 1],
                    ),
                  ),
                  Container(
                    height: 50,
                    width: double.infinity,
                    color: Colors.black,
                    child: SingleChildScrollView(
                      scrollDirection: Axis.horizontal,
                      child: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          for (int i = 0; i < widget.examples.length; i++)
                            Container(
                              width: 58,
                              padding: const EdgeInsets.only(left: 4, right: 4),
                              child: button(i + 1),
                            ),
                        ],
                      ),
                    ),
                  ),
                  Container(
                    height: 273,
                    color: Colors.grey[50],
                    child: Scrollbar(
                      child: SingleChildScrollView(
                        key: ValueKey(count),
                        child: Padding(
                          padding: const EdgeInsets.all(10),
                          child: Column(
                            children: [
                              Center(child: Text(code)),
                              const SizedBox(height: 15),
                              Text(
                                explanation,
                                style: TextStyle(
                                  color: Colors.blue[900],
                                  fontStyle: FontStyle.italic,
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget button(int exampleNumber) {
    return Button(
      key: ValueKey('button$exampleNumber'),
      isSelected: count == exampleNumber,
      exampleNumber: exampleNumber,
      onPressed: () {
        showExample(
          exampleNumber,
          widget.examples[exampleNumber - 1].code,
          widget.examples[exampleNumber - 1].explanation,
        );
      },
    );
  }

  void showExample(int exampleNumber, String code, String explanation) {
    setState(() {
      count = exampleNumber;
      this.code = code;
      this.explanation = explanation;
    });
  }
}

//////////////////////////////////////////////////

class Button extends StatelessWidget {
  final bool isSelected;
  final int exampleNumber;
  final VoidCallback onPressed;

  const Button({
    super.key,
    required this.isSelected,
    required this.exampleNumber,
    required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return TextButton(
      style: TextButton.styleFrom(
        foregroundColor: Colors.white,
        backgroundColor: isSelected ? Colors.grey : Colors.grey[800],
      ),
      child: Text(exampleNumber.toString()),
      onPressed: () {
        Scrollable.ensureVisible(
          context,
          duration: const Duration(milliseconds: 350),
          curve: Curves.easeOut,
          alignment: 0.5,
        );
        onPressed();
      },
    );
  }
}
//////////////////////////////////////////////////

class Example1 extends Example {
  const Example1({super.key});

  @override
  final code = 'Container(color: red)';

  @override
  final explanation =
      'The screen is the parent of the Container, '
      'and it forces the Container to be exactly the same size as the screen.'
      '\n\n'
      'So the Container fills the screen and paints it red.';

  @override
  Widget build(BuildContext context) {
    return Container(color: red);
  }
}

//////////////////////////////////////////////////

class Example2 extends Example {
  const Example2({super.key});

  @override
  final code = 'Container(width: 100, height: 100, color: red)';
  @override
  final String explanation =
      'The red Container wants to be 100x100, but it can\'t, '
      'because the screen forces it to be exactly the same size as the screen.'
      '\n\n'
      'So the Container fills the screen.';

  @override
  Widget build(BuildContext context) {
    return Container(width: 100, height: 100, color: red);
  }
}

//////////////////////////////////////////////////

class Example3 extends Example {
  const Example3({super.key});

  @override
  final code =
      'Center(\n'
      '   child: Container(width: 100, height: 100, color: red))';
  @override
  final String explanation =
      'The screen forces the Center to be exactly the same size as the screen, '
      'so the Center fills the screen.'
      '\n\n'
      'The Center tells the Container that it can be any size it wants, but not bigger than the screen.'
      'Now the Container can indeed be 100x100.';

  @override
  Widget build(BuildContext context) {
    return Center(child: Container(width: 100, height: 100, color: red));
  }
}

//////////////////////////////////////////////////

class Example4 extends Example {
  const Example4({super.key});

  @override
  final code =
      'Align(\n'
      '   alignment: Alignment.bottomRight,\n'
      '   child: Container(width: 100, height: 100, color: red))';
  @override
  final String explanation =
      'This is different from the previous example in that it uses Align instead of Center.'
      '\n\n'
      'Align also tells the Container that it can be any size it wants, but if there is empty space it won\'t center the Container. '
      'Instead, it aligns the Container to the bottom-right of the available space.';

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.bottomRight,
      child: Container(width: 100, height: 100, color: red),
    );
  }
}

//////////////////////////////////////////////////

class Example5 extends Example {
  const Example5({super.key});

  @override
  final code =
      'Center(\n'
      '   child: Container(\n'
      '              color: red,\n'
      '              width: double.infinity,\n'
      '              height: double.infinity))';
  @override
  final String explanation =
      'The screen forces the Center to be exactly the same size as the screen, '
      'so the Center fills the screen.'
      '\n\n'
      'The Center tells the Container that it can be any size it wants, but not bigger than the screen.'
      'The Container wants to be of infinite size, but since it can\'t be bigger than the screen, it just fills the screen.';

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        width: double.infinity,
        height: double.infinity,
        color: red,
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example6 extends Example {
  const Example6({super.key});

  @override
  final code = 'Center(child: Container(color: red))';
  @override
  final String explanation =
      'The screen forces the Center to be exactly the same size as the screen, '
      'so the Center fills the screen.'
      '\n\n'
      'The Center tells the Container that it can be any size it wants, but not bigger than the screen.'
      '\n\n'
      'Since the Container has no child and no fixed size, it decides it wants to be as big as possible, so it fills the whole screen.'
      '\n\n'
      'But why does the Container decide that? '
      'Simply because that\'s a design decision by those who created the Container widget. '
      'It could have been created differently, and you have to read the Container documentation to understand how it behaves, depending on the circumstances. ';

  @override
  Widget build(BuildContext context) {
    return Center(child: Container(color: red));
  }
}

//////////////////////////////////////////////////

class Example7 extends Example {
  const Example7({super.key});

  @override
  final code =
      'Center(\n'
      '   child: Container(color: red\n'
      '      child: Container(color: green, width: 30, height: 30)))';
  @override
  final String explanation =
      'The screen forces the Center to be exactly the same size as the screen, '
      'so the Center fills the screen.'
      '\n\n'
      'The Center tells the red Container that it can be any size it wants, but not bigger than the screen.'
      'Since the red Container has no size but has a child, it decides it wants to be the same size as its child.'
      '\n\n'
      'The red Container tells its child that it can be any size it wants, but not bigger than the screen.'
      '\n\n'
      'The child is a green Container that wants to be 30x30.'
      '\n\n'
      'Since the red `Container` has no size but has a child, it decides it wants to be the same size as its child. '
      'The red color isn\'t visible, since the green Container entirely covers all of the red Container.';

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: red,
        child: Container(color: green, width: 30, height: 30),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example8 extends Example {
  const Example8({super.key});

  @override
  final code =
      'Center(\n'
      '   child: Container(color: red\n'
      '      padding: const EdgeInsets.all(20),\n'
      '      child: Container(color: green, width: 30, height: 30)))';
  @override
  final String explanation =
      'The red Container sizes itself to its children size, but it takes its own padding into consideration. '
      'So it is also 30x30 plus padding. '
      'The red color is visible because of the padding, and the green Container has the same size as in the previous example.';

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        padding: const EdgeInsets.all(20),
        color: red,
        child: Container(color: green, width: 30, height: 30),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example9 extends Example {
  const Example9({super.key});

  @override
  final code =
      'ConstrainedBox(\n'
      '   constraints: BoxConstraints(\n'
      '              minWidth: 70, minHeight: 70,\n'
      '              maxWidth: 150, maxHeight: 150),\n'
      '      child: Container(color: red, width: 10, height: 10)))';
  @override
  final String explanation =
      'You might guess that the Container has to be between 70 and 150 pixels, but you would be wrong. '
      'The ConstrainedBox only imposes ADDITIONAL constraints from those it receives from its parent.'
      '\n\n'
      'Here, the screen forces the ConstrainedBox to be exactly the same size as the screen, '
      'so it tells its child Container to also assume the size of the screen, '
      'thus ignoring its \'constraints\' parameter.';

  @override
  Widget build(BuildContext context) {
    return ConstrainedBox(
      constraints: const BoxConstraints(
        minWidth: 70,
        minHeight: 70,
        maxWidth: 150,
        maxHeight: 150,
      ),
      child: Container(color: red, width: 10, height: 10),
    );
  }
}

//////////////////////////////////////////////////

class Example10 extends Example {
  const Example10({super.key});

  @override
  final code =
      'Center(\n'
      '   child: ConstrainedBox(\n'
      '      constraints: BoxConstraints(\n'
      '                 minWidth: 70, minHeight: 70,\n'
      '                 maxWidth: 150, maxHeight: 150),\n'
      '        child: Container(color: red, width: 10, height: 10))))';
  @override
  final String explanation =
      'Now, Center allows ConstrainedBox to be any size up to the screen size.'
      '\n\n'
      'The ConstrainedBox imposes ADDITIONAL constraints from its \'constraints\' parameter onto its child.'
      '\n\n'
      'The Container must be between 70 and 150 pixels. It wants to have 10 pixels, so it will end up having 70 (the MINIMUM).';

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          minWidth: 70,
          minHeight: 70,
          maxWidth: 150,
          maxHeight: 150,
        ),
        child: Container(color: red, width: 10, height: 10),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example11 extends Example {
  const Example11({super.key});

  @override
  final code =
      'Center(\n'
      '   child: ConstrainedBox(\n'
      '      constraints: BoxConstraints(\n'
      '                 minWidth: 70, minHeight: 70,\n'
      '                 maxWidth: 150, maxHeight: 150),\n'
      '        child: Container(color: red, width: 1000, height: 1000))))';
  @override
  final String explanation =
      'Center allows ConstrainedBox to be any size up to the screen size.'
      'The ConstrainedBox imposes ADDITIONAL constraints from its \'constraints\' parameter onto its child'
      '\n\n'
      'The Container must be between 70 and 150 pixels. It wants to have 1000 pixels, so it ends up having 150 (the MAXIMUM).';

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          minWidth: 70,
          minHeight: 70,
          maxWidth: 150,
          maxHeight: 150,
        ),
        child: Container(color: red, width: 1000, height: 1000),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example12 extends Example {
  const Example12({super.key});

  @override
  final code =
      'Center(\n'
      '   child: ConstrainedBox(\n'
      '      constraints: BoxConstraints(\n'
      '                 minWidth: 70, minHeight: 70,\n'
      '                 maxWidth: 150, maxHeight: 150),\n'
      '        child: Container(color: red, width: 100, height: 100))))';
  @override
  final String explanation =
      'Center allows ConstrainedBox to be any size up to the screen size.'
      'ConstrainedBox imposes ADDITIONAL constraints from its \'constraints\' parameter onto its child.'
      '\n\n'
      'The Container must be between 70 and 150 pixels. It wants to have 100 pixels, and that\'s the size it has, since that\'s between 70 and 150.';

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          minWidth: 70,
          minHeight: 70,
          maxWidth: 150,
          maxHeight: 150,
        ),
        child: Container(color: red, width: 100, height: 100),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example13 extends Example {
  const Example13({super.key});

  @override
  final code =
      'UnconstrainedBox(\n'
      '   child: Container(color: red, width: 20, height: 50));';
  @override
  final String explanation =
      'The screen forces the UnconstrainedBox to be exactly the same size as the screen.'
      'However, the UnconstrainedBox lets its child Container be any size it wants.';

  @override
  Widget build(BuildContext context) {
    return UnconstrainedBox(
      child: Container(color: red, width: 20, height: 50),
    );
  }
}

//////////////////////////////////////////////////

class Example14 extends Example {
  const Example14({super.key});

  @override
  final code =
      'UnconstrainedBox(\n'
      '   child: Container(color: red, width: 4000, height: 50));';
  @override
  final String explanation =
      'The screen forces the UnconstrainedBox to be exactly the same size as the screen, '
      'and UnconstrainedBox lets its child Container be any size it wants.'
      '\n\n'
      'Unfortunately, in this case the Container has 4000 pixels of width and is too big to fit in the UnconstrainedBox, '
      'so the UnconstrainedBox displays the much dreaded "overflow warning".';

  @override
  Widget build(BuildContext context) {
    return UnconstrainedBox(
      child: Container(color: red, width: 4000, height: 50),
    );
  }
}

//////////////////////////////////////////////////

class Example15 extends Example {
  const Example15({super.key});

  @override
  final code =
      'OverflowBox(\n'
      '   minWidth: 0,'
      '   minHeight: 0,'
      '   maxWidth: double.infinity,'
      '   maxHeight: double.infinity,'
      '   child: Container(color: red, width: 4000, height: 50));';
  @override
  final String explanation =
      'The screen forces the OverflowBox to be exactly the same size as the screen, '
      'and OverflowBox lets its child Container be any size it wants.'
      '\n\n'
      'OverflowBox is similar to UnconstrainedBox, and the difference is that it won\'t display any warnings if the child doesn\'t fit the space.'
      '\n\n'
      'In this case the Container is 4000 pixels wide, and is too big to fit in the OverflowBox, '
      'but the OverflowBox simply shows as much as it can, with no warnings given.';

  @override
  Widget build(BuildContext context) {
    return OverflowBox(
      minWidth: 0,
      minHeight: 0,
      maxWidth: double.infinity,
      maxHeight: double.infinity,
      child: Container(color: red, width: 4000, height: 50),
    );
  }
}

//////////////////////////////////////////////////

class Example16 extends Example {
  const Example16({super.key});

  @override
  final code =
      'UnconstrainedBox(\n'
      '   child: Container(color: Colors.red, width: double.infinity, height: 100));';
  @override
  final String explanation =
      'This won\'t render anything, and you\'ll see an error in the console.'
      '\n\n'
      'The UnconstrainedBox lets its child be any size it wants, '
      'however its child is a Container with infinite size.'
      '\n\n'
      'Flutter can\'t render infinite sizes, so it throws an error with the following message: '
      '"BoxConstraints forces an infinite width."';

  @override
  Widget build(BuildContext context) {
    return UnconstrainedBox(
      child: Container(color: Colors.red, width: double.infinity, height: 100),
    );
  }
}

//////////////////////////////////////////////////

class Example17 extends Example {
  const Example17({super.key});

  @override
  final code =
      'UnconstrainedBox(\n'
      '   child: LimitedBox(maxWidth: 100,\n'
      '      child: Container(color: Colors.red,\n'
      '                       width: double.infinity, height: 100));';
  @override
  final String explanation =
      'Here you won\'t get an error anymore, '
      'because when the LimitedBox is given an infinite size by the UnconstrainedBox, '
      'it passes a maximum width of 100 down to its child.'
      '\n\n'
      'If you swap the UnconstrainedBox for a Center widget, '
      'the LimitedBox won\'t apply its limit anymore (since its limit is only applied when it gets infinite constraints), '
      'and the width of the Container is allowed to grow past 100.'
      '\n\n'
      'This explains the difference between a LimitedBox and a ConstrainedBox.';

  @override
  Widget build(BuildContext context) {
    return UnconstrainedBox(
      child: LimitedBox(
        maxWidth: 100,
        child: Container(
          color: Colors.red,
          width: double.infinity,
          height: 100,
        ),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example18 extends Example {
  const Example18({super.key});

  @override
  final code =
      'FittedBox(\n'
      '   child: Text(\'Some Example Text.\'));';
  @override
  final String explanation =
      'The screen forces the FittedBox to be exactly the same size as the screen.'
      'The Text has some natural width (also called its intrinsic width) that depends on the amount of text, its font size, and so on.'
      '\n\n'
      'The FittedBox lets the Text be any size it wants, '
      'but after the Text tells its size to the FittedBox, '
      'the FittedBox scales the Text until it fills all of the available width.';

  @override
  Widget build(BuildContext context) {
    return const FittedBox(child: Text('Some Example Text.'));
  }
}

//////////////////////////////////////////////////

class Example19 extends Example {
  const Example19({super.key});

  @override
  final code =
      'Center(\n'
      '   child: FittedBox(\n'
      '      child: Text(\'Some Example Text.\')));';
  @override
  final String explanation =
      'But what happens if you put the FittedBox inside of a Center widget? '
      'The Center lets the FittedBox be any size it wants, up to the screen size.'
      '\n\n'
      'The FittedBox then sizes itself to the Text, and lets the Text be any size it wants.'
      '\n\n'
      'Since both FittedBox and the Text have the same size, no scaling happens.';

  @override
  Widget build(BuildContext context) {
    return const Center(child: FittedBox(child: Text('Some Example Text.')));
  }
}

////////////////////////////////////////////////////

class Example20 extends Example {
  const Example20({super.key});

  @override
  final code =
      'Center(\n'
      '   child: FittedBox(\n'
      '      child: Text(\'…\')));';
  @override
  final String explanation =
      'However, what happens if FittedBox is inside of a Center widget, but the Text is too large to fit the screen?'
      '\n\n'
      'FittedBox tries to size itself to the Text, but it can\'t be bigger than the screen. '
      'It then assumes the screen size, and resizes Text so that it fits the screen, too.';

  @override
  Widget build(BuildContext context) {
    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.',
        ),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example21 extends Example {
  const Example21({super.key});

  @override
  final code =
      'Center(\n'
      '   child: Text(\'…\'));';
  @override
  final String explanation =
      'If, however, you remove the FittedBox, '
      'the Text gets its maximum width from the screen, '
      'and breaks the line so that it fits the screen.';

  @override
  Widget build(BuildContext context) {
    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.',
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example22 extends Example {
  const Example22({super.key});

  @override
  final code =
      'FittedBox(\n'
      '   child: Container(\n'
      '      height: 20, width: double.infinity));';
  @override
  final String explanation =
      'FittedBox can only scale a widget that is BOUNDED (has non-infinite width and height).'
      'Otherwise, it won\'t render anything, and you\'ll see an error in the console.';

  @override
  Widget build(BuildContext context) {
    return FittedBox(
      child: Container(height: 20, width: double.infinity, color: Colors.red),
    );
  }
}

//////////////////////////////////////////////////

class Example23 extends Example {
  const Example23({super.key});

  @override
  final code =
      'Row(children:[\n'
      '   Container(color: red, child: Text(\'Hello!\'))\n'
      '   Container(color: green, child: Text(\'Goodbye!\'))]';
  @override
  final String explanation =
      'The screen forces the Row to be exactly the same size as the screen.'
      '\n\n'
      'Just like an UnconstrainedBox, the Row won\'t impose any constraints onto its children, '
      'and instead lets them be any size they want.'
      '\n\n'
      'The Row then puts them side-by-side, and any extra space remains empty.';

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Container(
          color: red,
          child: const Text('Hello!', style: big),
        ),
        Container(
          color: green,
          child: const Text('Goodbye!', style: big),
        ),
      ],
    );
  }
}

//////////////////////////////////////////////////

class Example24 extends Example {
  const Example24({super.key});

  @override
  final code =
      'Row(children:[\n'
      '   Container(color: red, child: Text(\'…\'))\n'
      '   Container(color: green, child: Text(\'Goodbye!\'))]';
  @override
  final String explanation =
      'Since the Row won\'t impose any constraints onto its children, '
      'it\'s quite possible that the children might be too big to fit the available width of the Row.'
      'In this case, just like an UnconstrainedBox, the Row displays the "overflow warning".';

  @override
  Widget build(BuildContext context) {
    return 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),
        ),
      ],
    );
  }
}

//////////////////////////////////////////////////

class Example25 extends Example {
  const Example25({super.key});

  @override
  final code =
      'Row(children:[\n'
      '   Expanded(\n'
      '       child: Container(color: red, child: Text(\'…\')))\n'
      '   Container(color: green, child: Text(\'Goodbye!\'))]';
  @override
  final String explanation =
      'When a Row\'s child is wrapped in an Expanded widget, the Row won\'t let this child define its own width anymore.'
      '\n\n'
      'Instead, it defines the Expanded width according to the other children, and only then the Expanded widget forces the original child to have the Expanded\'s width.'
      '\n\n'
      'In other words, once you use Expanded, the original child\'s width becomes irrelevant, and is ignored.';

  @override
  Widget build(BuildContext context) {
    return 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: const Text('Goodbye!', style: big),
        ),
      ],
    );
  }
}

//////////////////////////////////////////////////

class Example26 extends Example {
  const Example26({super.key});

  @override
  final code =
      'Row(children:[\n'
      '   Expanded(\n'
      '       child: Container(color: red, child: Text(\'…\')))\n'
      '   Expanded(\n'
      '       child: Container(color: green, child: Text(\'Goodbye!\'))]';
  @override
  final String explanation =
      'If all of Row\'s children are wrapped in Expanded widgets, each Expanded has a size proportional to its flex parameter, '
      'and only then each Expanded widget forces its child to have the Expanded\'s width.'
      '\n\n'
      'In other words, Expanded ignores the preferred width of its children.';

  @override
  Widget build(BuildContext context) {
    return 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),
          ),
        ),
      ],
    );
  }
}

//////////////////////////////////////////////////

class Example27 extends Example {
  const Example27({super.key});

  @override
  final code =
      'Row(children:[\n'
      '   Flexible(\n'
      '       child: Container(color: red, child: Text(\'…\')))\n'
      '   Flexible(\n'
      '       child: Container(color: green, child: Text(\'Goodbye!\'))]';
  @override
  final String explanation =
      'The only difference if you use Flexible instead of Expanded, '
      'is that Flexible lets its child be SMALLER than the Flexible width, '
      'while Expanded forces its child to have the same width of the Expanded.'
      '\n\n'
      'But both Expanded and Flexible ignore their children\'s width when sizing themselves.'
      '\n\n'
      'This means that it\'s IMPOSSIBLE to expand Row children proportionally to their sizes. '
      'The Row either uses the exact child\'s width, or ignores it completely when you use Expanded or Flexible.';

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Flexible(
          child: Container(
            color: red,
            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),
          ),
        ),
      ],
    );
  }
}

//////////////////////////////////////////////////

class Example28 extends Example {
  const Example28({super.key});

  @override
  final code =
      'Scaffold(\n'
      '   body: Container(color: blue,\n'
      '   child: Column(\n'
      '      children: [\n'
      '         Text(\'Hello!\'),\n'
      '         Text(\'Goodbye!\')])))';

  @override
  final String explanation =
      'The screen forces the Scaffold to be exactly the same size as the screen, '
      'so the Scaffold fills the screen.'
      '\n\n'
      'The Scaffold tells the Container that it can be any size it wants, but not bigger than the screen.'
      '\n\n'
      'When a widget tells its child that it can be smaller than a certain size, '
      'we say the widget supplies "loose" constraints to its child. More on that later.';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: blue,
        child: const Column(children: [Text('Hello!'), Text('Goodbye!')]),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example29 extends Example {
  const Example29({super.key});

  @override
  final code =
      'Scaffold(\n'
      '   body: Container(color: blue,\n'
      '   child: SizedBox.expand(\n'
      '      child: Column(\n'
      '         children: [\n'
      '            Text(\'Hello!\'),\n'
      '            Text(\'Goodbye!\')]))))';

  @override
  final String explanation =
      'If you want the Scaffold\'s child to be exactly the same size as the Scaffold itself, '
      'you can wrap its child with SizedBox.expand.'
      '\n\n'
      'When a widget tells its child that it must be of a certain size, '
      'we say the widget supplies "tight" constraints to its child. More on that later.';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SizedBox.expand(
        child: Container(
          color: blue,
          child: const Column(children: [Text('Hello!'), Text('Goodbye!')]),
        ),
      ),
    );
  }
}

//////////////////////////////////////////////////

如果您愿意,可以从 这个 GitHub 仓库 获取代码。

以下章节将解释这些示例。

示例 1

#
Example 1 layout
dart
Container(color: red)

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

因此,Container 填充屏幕并将其涂成红色。

示例 2

#
Example 2 layout
dart
Container(width: 100, height: 100, color: red)

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

因此,Container 填充屏幕。

示例 3

#
Example 3 layout
dart
Center(child: Container(width: 100, height: 100, color: red))

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

Center 告诉 Container 它可以拥有任何它想要的大小,但不能大于屏幕。现在 Container 确实可以成为 100 × 100。

示例 4

#
Example 4 layout
dart
Align(
  alignment: Alignment.bottomRight,
  child: Container(width: 100, height: 100, color: red),
)

这与前一个示例不同,因为它使用 Align 代替 Center

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

示例 5

#
Example 5 layout
dart
Center(
  child: Container(
    width: double.infinity,
    height: double.infinity,
    color: red,
  ),
)

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

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

示例 6

#
Example 6 layout
dart
Center(child: Container(color: red))

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

Center 告诉 Container 它可以拥有任何它想要的大小,但不能大于屏幕。由于 Container 没有子组件也没有固定大小,它决定它想要尽可能大,所以它填充整个屏幕。

但是为什么 Container 决定这样做?仅仅是因为那些创建 Container 小部件的人做出的设计决策。它可以被创建得不同,你必须阅读 Container API 文档才能了解它在不同情况下的行为。

示例 7

#
Example 7 layout
dart
Center(
  child: Container(
    color: red,
    child: Container(color: green, width: 30, height: 30),
  ),
)

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

Center 告诉红色的 Container 它可以拥有任何它想要的大小,但不能大于屏幕。由于红色的 Container 没有大小但有子组件,它决定它想要与它的子组件大小相同。

红色的 Container 告诉它的子组件它可以拥有任何它想要的大小,但不能大于屏幕。

子组件是一个绿色的 Container,想要是 30 × 30。鉴于红色的 Container 将自身调整为子组件的大小,它也是 30 × 30。红色不可见,因为绿色的 Container 完全覆盖了红色的 Container

示例 8

#
Example 8 layout
dart
Center(
  child: Container(
    padding: const EdgeInsets.all(20),
    color: red,
    child: Container(color: green, width: 30, height: 30),
  ),
)

红色的 Container 将自身调整为子组件的大小,但它会考虑它自己的填充。所以它也是 30 × 30 加上填充。由于填充,红色可见,并且绿色的 Container 与前一个示例中的大小相同。

示例 9

#
Example 9 layout
dart
ConstrainedBox(
  constraints: const BoxConstraints(
    minWidth: 70,
    minHeight: 70,
    maxWidth: 150,
    maxHeight: 150,
  ),
  child: Container(color: red, width: 10, height: 10),
)

你可能会猜想 Container 必须在 70 到 150 像素之间,但你会错了。 ConstrainedBox 仅施加来自其父组件的附加约束。

在这里,屏幕强制 ConstrainedBox 与屏幕完全相同的大小,所以它告诉它的子 Container 也假设屏幕的大小,从而忽略了它的 constraints 参数。

示例 10

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

现在,Center 允许 ConstrainedBox 拥有任何大小,直到屏幕大小。 ConstrainedBox 从其 constraints 参数施加附加约束到其子组件。

Container 必须在 70 到 150 像素之间。它想要有 10 像素,所以它最终有 70(最小值)。

示例 11

#
Example 11 layout
dart
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 从其 constraints 参数施加附加约束到其子组件。

Container 必须在 70 到 150 像素之间。它想要有 1000 像素,所以它最终有 150(最大值)。

示例 12

#
Example 12 layout
dart
Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      minWidth: 70,
      minHeight: 70,
      maxWidth: 150,
      maxHeight: 150,
    ),
    child: Container(color: red, width: 100, height: 100),
  ),
)

Center 允许 ConstrainedBox 拥有任何大小,直到屏幕大小。 ConstrainedBox 从其 constraints 参数施加附加约束到其子组件。

Container 必须在 70 到 150 像素之间。它想要有 100 像素,并且由于它在 70 到 150 之间,所以它就是这个大小。

示例 13

#
Example 13 layout
dart
UnconstrainedBox(
  child: Container(color: red, width: 20, height: 50),
)

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

示例 14

#
Example 14 layout
dart
UnconstrainedBox(
  child: Container(color: red, width: 4000, height: 50),
)

屏幕强制 UnconstrainedBox 与屏幕完全相同的大小,并且 UnconstrainedBox 允许其子 Container 拥有任何它想要的大小。

不幸的是,在这种情况下,Container 的宽度为 4000 像素,并且太大而无法容纳在 UnconstrainedBox 中,因此 UnconstrainedBox 显示了令人头疼的“溢出警告”。

示例 15

#
Example 15 layout
dart
OverflowBox(
  minWidth: 0,
  minHeight: 0,
  maxWidth: double.infinity,
  maxHeight: double.infinity,
  child: Container(color: red, width: 4000, height: 50),
)

屏幕强制 OverflowBox 与屏幕完全相同的大小,并且 OverflowBox 允许其子 Container 拥有任何它想要的大小。

OverflowBox 类似于 UnconstrainedBox;区别在于,如果子组件不适合空间,它不会显示任何警告。

在这种情况下,Container 的宽度为 4000 像素,并且太大而无法容纳在 OverflowBox 中,但 OverflowBox 只是显示尽可能多的内容,没有给出任何警告。

示例 16

#
Example 16 layout
dart
UnconstrainedBox(
  child: Container(color: Colors.red, width: double.infinity, height: 100),
)

这不会渲染任何内容,你会在控制台中看到一个错误。

UnconstrainedBox 允许其子组件拥有任何它想要的大小,但是它的子组件是一个具有无限大小的 Container

Flutter 无法渲染无限尺寸,因此会抛出带有以下消息的错误:BoxConstraints 强制使用无限宽度。

示例 17

#
Example 17 layout
dart
UnconstrainedBox(
  child: LimitedBox(
    maxWidth: 100,
    child: Container(
      color: Colors.red,
      width: double.infinity,
      height: 100,
    ),
  ),
)

在这里,你将不再收到错误,因为当 LimitedBoxUnconstrainedBox 获得无限尺寸时,它会将最大宽度 100 传递给其子组件。

如果你将 UnconstrainedBox 替换为 Center 组件,LimitedBox 将不再应用其限制(因为其限制仅在获得无限约束时才应用),并且 Container 的宽度将被允许增长超过 100。

这解释了 LimitedBoxConstrainedBox 之间的区别。

示例 18

#
Example 18 layout
dart
const FittedBox(child: Text('Some Example Text.'))

屏幕强制 FittedBox 的大小与屏幕完全相同。Text 有一定的固有宽度(也称为其内在宽度),该宽度取决于文本量、字体大小等。

FittedBox 允许 Text 具有任何它想要的大小,但在 Text 将其大小告知 FittedBox 之后,FittedBox 会缩放文本,直到它填充所有可用宽度。

示例 19

#
Example 19 layout
dart
const Center(child: FittedBox(child: Text('Some Example Text.')))

但是,如果你将 FittedBox 放在 Center 组件内部会发生什么?Center 允许 FittedBox 具有任何它想要的大小,最多到屏幕大小。

然后,FittedBox 将自身调整为 Text 的大小,并允许 Text 具有任何它想要的大小。由于 FittedBoxText 具有相同的大小,因此不会发生缩放。

示例 20

#
Example 20 layout
dart
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 位于 Center 组件内部,但 Text 太大而无法适应屏幕会发生什么?

FittedBox 尝试将其自身调整为 Text 的大小,但它不能大于屏幕。然后它假定屏幕大小,并调整 Text 的大小,使其也适合屏幕。

示例 21

#
Example 21 layout
dart
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.',
  ),
)

但是,如果你删除 FittedBoxText 将从屏幕获得其最大宽度,并断行以使其适应屏幕。

示例 22

#
Example 22 layout
dart
FittedBox(
  child: Container(height: 20, width: double.infinity, color: Colors.red),
)

FittedBox 只能缩放具有边界(具有非无限宽度和高度)的组件。否则,它将不会渲染任何内容,并且你会在控制台中看到一个错误。

示例 23

#
Example 23 layout
dart
Row(
  children: [
    Container(
      color: red,
      child: const Text('Hello!', style: big),
    ),
    Container(
      color: green,
      child: const Text('Goodbye!', style: big),
    ),
  ],
)

屏幕强制 Row 的大小与屏幕完全相同。

就像 UnconstrainedBox 一样,Row 不会对它的子组件施加任何约束,而是让它们具有任何它们想要的大小。然后,Row 将它们并排放置,并且任何额外的空间都保持为空。

示例 24

#
Example 24 layout
dart
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 会显示“溢出警告”。

示例 25

#
Example 25 layout
dart
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: const Text('Goodbye!', style: big),
    ),
  ],
)

Row 的子组件包装在 Expanded 组件中时,Row 不允许该子组件定义自己的宽度。

相反,它根据其他子组件定义 Expanded 的宽度,然后 Expanded 组件强制原始子组件具有 Expanded 的宽度。

换句话说,一旦你使用 Expanded,原始子组件的宽度变得无关紧要,并且会被忽略。

示例 26

#
Example 26 layout
dart
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 忽略其子组件的首选宽度。

示例 27

#
Example 27 layout
dart
Row(
  children: [
    Flexible(
      child: Container(
        color: red,
        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,唯一的区别是 Flexible 允许其子组件具有与 Flexible 自身相同或更小的宽度,而 Expanded 强制其子组件具有与 Expanded 相同的宽度。但是,ExpandedFlexible 在调整自身大小时都会忽略其子组件的宽度。

示例 28

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

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

示例 29

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

如果你想让 Scaffold 的子组件的大小与 Scaffold 自身完全相同,你可以使用 SizedBox.expand 包装其子组件。

紧约束与宽松约束

#

经常听到某些约束是“紧约束”或“宽松约束”,那么这意味着什么?

紧约束

#

紧约束提供一个单一的可能性,即确切的大小。换句话说,紧约束的其最大宽度等于其最小宽度;并且其最大高度等于其最小高度。

一个例子是 App 组件,它包含在 RenderView 类中:应用程序的 build 函数返回的子组件使用的框被赋予一个约束,该约束强制它完全填充应用程序的内容区域(通常是整个屏幕)。

另一个例子:如果你在应用程序的渲染树的根目录中嵌套一堆框,它们将全部精确地相互适应,受到框的紧约束的强制。

如果你转到 Flutter 的 box.dart 文件并搜索 BoxConstraints 构造函数,你将找到以下内容

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

如果你重新访问 示例 2,屏幕强制红色 Container 的大小与屏幕完全相同。屏幕通过将紧约束传递给 Container 来实现这一点。

宽松约束

#

宽松约束是指具有最小值为零且最大值为非零的约束。

一些框放宽传入的约束,这意味着保留最大值,但删除最小值,因此组件可以具有最小宽度和高度都等于

最终,Center 的目的是将它从其父组件(屏幕)收到的紧约束转换为其子组件(Container)的宽松约束。

如果你重新访问 示例 3Center 允许红色 Container 变小,但不能大于屏幕。

无边界约束

#

在某些情况下,框的约束是无边界的或无限的。这意味着最大宽度或最大高度设置为 double.infinity

当给定无边界约束时,试图尽可能大的框将无法正常工作,并且在调试模式下会抛出异常。

渲染框最终获得无边界约束的最常见情况是在 flex 框(RowColumn)内,以及在可滚动区域内(例如 ListView 和其他 ScrollView 子类)。

例如,ListView 尝试扩展以适应其交叉方向中的可用空间(也许它是一个垂直滚动块,并尝试与它的父组件一样宽)。如果你将垂直滚动的 ListView 嵌套在水平滚动的 ListView 中,则内部列表尝试尽可能宽,这会无限宽,因为外部列表在该方向上是可滚动的。

下一节描述了你在 Flex 组件中遇到无边界约束时可能遇到的错误。

Flex

#

Flex 框(RowColumn)根据其主要方向上的约束是边界还是无边界,表现不同。

具有其主要方向上边界约束的 flex 框尝试尽可能大。

具有其主要方向上无边界约束的 flex 框尝试将其子组件适应到该空间中。每个子组件的 flex 值必须设置为零,这意味着你不能在 flex 框位于另一个 flex 框或可滚动区域内时使用 Expanded;否则它会抛出异常。

交叉方向(对于 Column 而言是宽度,对于 Row 而言是高度)绝不能是无边界的,否则它无法合理地对齐其子组件。

学习特定小部件的布局规则

#

了解一般的布局规则是必要的,但还不够。

每个小部件在应用一般规则时都有很大的自由度,因此仅通过阅读小部件的名称无法知道它的行为方式。

如果你尝试猜测,你可能会猜错。除非你阅读了它的文档或研究了它的源代码,否则你无法确切地知道一个小部件的行为方式。

布局源代码通常很复杂,因此最好阅读文档。但是,如果你决定研究布局源代码,你可以轻松地使用 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() 的方法。这是执行 Column 布局的方法。

A goodbye layout

Marcelo Glasberg 的原始文章

Marcelo 最初将此内容作为 Flutter: The Advanced Layout Rule Even Beginners Must Know 在 Medium 上发布。我们非常喜欢它,并要求他允许我们在 docs.flutter.dev 上发布,他欣然同意。谢谢你,Marcelo!你可以在 GitHubpub.dev 上找到 Marcelo。

此外,感谢 Simon Lightfoot 创建文章顶部的页眉图片。