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可以是它想要的任何尺寸。

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

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

示例 16

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

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

UnconstrainedBox让其子级可以是它想要的任何尺寸,然而它的子级是一个具有无限尺寸的Container

Flutter 无法渲染无限尺寸,所以它会抛出一个错误,并显示以下消息:BoxConstraints forces an infinite width.(BoxConstraints 强制宽度为无限。)

示例 17

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

在这里你将不再收到错误,因为当UnconstrainedBoxLimitedBox一个无限尺寸时;它将最大宽度 100 传递给其子级。

如果你用Center小部件替换UnconstrainedBoxLimitedBox将不再应用其限制(因为它的限制只在它接收到无限约束时才应用),并且Container的宽度被允许增长超过 100。

这解释了LimitedBoxConstrainedBox之间的区别。

示例 18

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

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

FittedBoxText可以是它想要的任何尺寸,但在Text将其尺寸告知FittedBox之后,FittedBox会缩放 Text,直到它填充所有可用宽度。

示例 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.',
    ),
  ),
)

然而,如果FittedBoxCenter小部件内部,但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

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

渲染盒子最终出现无界约束的最常见情况是在弹性盒子(RowColumn)内部,以及可滚动区域(如ListView和其他ScrollView子类)内部。

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

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

弹性盒子

#

弹性盒子(RowColumn)的行为因其主方向上的约束是有界还是无界而异。

在主方向上具有有界约束的弹性盒子会尝试尽可能大。

在主方向上具有无界约束的弹性盒子会尝试在该空间内适应其子级。每个子级的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 最初在 Medium 上发表了这篇内容,题为Flutter:即使是初学者也必须了解的进阶布局规则。我们很喜欢它,并请求他允许我们在 docs.flutter.dev 上发布,他欣然同意。谢谢你,Marcelo!你可以在 GitHubpub.dev 上找到 Marcelo。

此外,感谢Simon Lightfoot创作了文章顶部的标题图片。