跳至主要內容

瞭解約束條件

Hero image from the article

當有人學習 Flutter 時,問您為什麼某些具有 width: 100 的小部件不是 100 像素寬時,預設的答案是告訴他們將該小部件放在 Center 裡面,對嗎?

不要那樣做。

如果您這樣做,他們會一次又一次地回來,詢問為什麼某些 FittedBox 沒有作用、為什麼 Column 溢位,或者 IntrinsicWidth 應該做什麼。

相反地,首先告訴他們 Flutter 佈局與 HTML 佈局(他們可能來自這裡)非常不同,然後讓他們記住以下規則

約束條件向下傳遞。尺寸向上傳遞。父級設定位置。

如果不了解此規則,就無法真正理解 Flutter 佈局,因此 Flutter 開發人員應該儘早學習它。

更詳細地說

  • 小部件從其 父級 取得自己的 約束條件約束條件 只是一組 4 個 double 值:最小和最大寬度,以及最小和最大高度。
  • 然後,小部件會逐一檢查自己的 子項 列表。小部件會告訴其子項它們的 約束條件 是什麼(每個子項的約束條件可能不同),然後詢問每個子項它想要多大的尺寸。
  • 然後,小部件會逐一放置其 子項 (水平方向在 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 中的許多 box,尤其是那些只接受單個子項的 box,會將其約束條件傳遞給其子項。

一般來說,就它們如何處理約束條件而言,有三種類型的 box

  • 那些試圖盡可能大的 box。例如,CenterListView 使用的 box。
  • 那些試圖與其子項相同大小的 box。例如,TransformOpacity 使用的 box。
  • 那些試圖成為特定大小的 box。例如,ImageText 使用的 box。

某些小部件,例如 Container,會根據其建構子引數而有所不同。Container 建構子的預設行為是嘗試盡可能大,但如果您為其指定一個 width,例如,它會嘗試遵守該值並成為該特定大小。

其他小部件,例如 RowColumn(彈性 box)會根據它們獲得的約束條件而有所不同,如 彈性 章節所述。

範例

#

如需互動式體驗,請使用以下 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 像素,而這也是它的大小,因為 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,
    ),
  ),
)

在這裡,您將不再收到錯誤,因為當 LimitedBoxUnconstrainedBox 獲得無限大小時;它會將最大寬度 100 傳遞給其子元件。

如果您將 UnconstrainedBox 換成 Center 小工具,LimitedBox 將不再套用其限制(因為它的限制僅在獲得無限約束時才套用),並且允許 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 包裝其子元件。

嚴格 vs 寬鬆約束

#

很常見會聽到某些約束是「嚴格」或「寬鬆」,那麼這代表什麼意思呢?

嚴格約束

#

嚴格約束提供單一的可能性,即確切的大小。換句話說,嚴格約束的最大寬度等於其最小寬度;而且其最大高度等於其最小高度。

這方面的一個範例是 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 小部件時可能遇到的無邊界約束錯誤。

Flex

#

彈性盒子(flex box)(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: The Advanced Layout Rule Even Beginners Must Know 的形式發佈了此內容。我們很喜歡它,並要求他允許我們在 docs.flutter.dev 上發佈,他慷慨地同意了。謝謝,Marcelo!您可以在 GitHubpub.dev 上找到 Marcelo。

此外,感謝 Simon Lightfoot 創建了本文頂部的標頭圖片。