This article, translated from 👉Understanding Constraints, reconstructs the layout of Flutter through more than twenty cases to help you.

By Marcelo Glasberg


Sometimes people who learn about Flutter ask why some widgets have width:100 but do not display a pixel width of 100. Someone replies: Put that widget in the Center component. Isn’t it.

Don’t answer that.

If so, they will ask again and again why some FittedBoxes are not working, why some columns are out of scope, and why IntrinsicWidth should do something.

Instead, first tell them that the layout of Flutter is very different from the HTML layout and then make them remember the following layout rules:

Pass the constraint down to 👇. Pass the dimensions up to 👆. The parent node sets the location

If you don’t understand the rules above. The Flutter layout cannot be truly understood, so developers should understand this rule as soon as possible. More details:

  • Widget gets its own constraints from its parent. Constraint is set by four double values: minimum and maximum width, and minimum and maximum height.

  • Widget then walks through all of his child nodes, telling them the constraints one by one. Each child node has a different constraint, and asking what size each child node wants.

  • Again, Widget places its child nodes one by one, using the X axis horizontally and the Y axis vertically.

  • Finally, the Widget tells its parent its own size, which is within the constraints of the first step.

For example 🌰, a composite Widget contains a Column with Padding spacing and wants to place its two child nodes as shown below:

The layout process is like the following dialogue:

Widget: “Hey parent, what are my constraints”

Parent node: “Your width must be in the 80 to 300 pixel range and your height must be in the 80 to 300 pixel range”

Widget: “Hmmm, since I want to have a 5-pixel pitch, my child nodes are at most 290 pixels wide and 75 pixels high”

Widget: “Hey first child node, you must be 0 to 290 pixels wide and 0 to 75 pixels tall”

First Child: “OK, I want my width to be 290 pixels and my height to be 20 pixels”

Widget: “Hmmm, because I want to put the second child below the first child, so that only gives the second child 55 pixels height”

Widget: “Hey second child node, you must be 0 to 290 pixels wide and 0 to 55 pixels tall”

Second Child: “OK, I want my width to be 140 pixels and my height to be 30 pixels”

Widget: “Great. The position of the first child is x: 5, y: 5, and the position of the second child is x: 80, y: 25.”

Widget: “Hey parent node, I have decided my size, width is 300 pixels, height is 60 pixels”

limit

Due to the layout rules mentioned above, the Flutter layout engine has several important limitations:

  • A Widget can only determine the size information of bytes within the constraints given by its parent node, which means that widgets usually cannot have the size information they want

  • Since the parent node determines the location of the child node, a Widget does not know and cannot determine the location of a byte on the screen

  • Because the size and location information of a parent node also depends on its parent, it is impossible to define the size and location information of any component precisely without considering the component tree as a whole

  • If a child wants a different size from its parent and the parent doesn’t have enough information to align it, Then the child’s size might be ignored. Be specific when defining alignment

If the child node’s desired size is different from the parent node’s given size range, and the parent node does not have enough information to align the child nodes, then the child node’s size appeal is ignored. The specific size information defines the alignment information

case

DartPad can be used to run the case code. There are 29 cases, and each case is numbered. 👉 Case code

Example 1

Container(color: red)
Copy the code

The screen is the parent node of the Container. The screen forces the Container to be the same size as Screen.

Therefore, the Container fills the screen with red.

Example 2

Container(width: 100, height: 100, color: red)
Copy the code

The Container wants 100 by 100, but it doesn’t. The screen forces the Container to be the same size as Screen. Therefore, the Container still fills the screen with red.

Example 3

Center(
  child: Container(width: 100, height: 100, color: red),
)
Copy the code

The screen forces the size of Center to be the size of the screen, so Center fills the entire screen.

Center tells the Container that it can be any size it wants to be on the screen.

Therefore, the Container can be 100 x 100.

Example 4

Align(
  alignment: Alignment.bottomRight,
  child: Container(width: 100, height: 100, color: red),
)
Copy the code

Unlike the previous example, Align is used instead of the Center component.

Similar to the previous example, Align tells the Container to be on screen and that it can be any size. Align the Container with the lower right corner of Align, relative to the center, in the range available.

Example 5

Center(
  child: Container(
      width: double.infinity, height: double.infinity, color: red),
)
Copy the code

The screen forces the size of Center to be the size of the screen, so Center fills the entire screen.

Center tells the Container that it can be any size it wants to be on the screen.

The Container wants unlimited size information, but since it cannot exceed the screen.

So: The Container just fills the entire screen.

Example 6

Center(
  child: Container(color: red),
)
Copy the code

The screen forces the size of Center to be the size of the screen, so Center fills the entire screen.

Center tells the Container that it can be any size it wants to be on the screen. Since the Container has no child nodes and no fixed size, it decides it wants to be as large as possible, so it fills the entire screen.

But why did Container decide to make it as big as possible? Because that’s what the component designer thinks 💡. For details, see the 👉Container documentation

Example 7

Center(
  child: Container(
    color: red,
    child: Container(color: green, width: 30, height: 30),),)Copy the code

The screen forces the size of Center to be the size of the screen, so Center fills the entire screen.

Center tells the Container that it can be any size it wants to be on the screen. Because the red Container has no size information, but it has a child node. The Container decides that it wants the same size as the Child.

The red Container tells its child nodes that it can have any size it wants within the screen.

The size of the green Container is 30 x 30. Assuming that the red Container resizes itself to the size of its children, it is also 30 × 30. Because the red is completely covered by the green, the red Container is invisible.

Example 8

Center(
  child: Container(
    padding: const EdgeInsets.all(20.0),
    color: red,
    child: Container(color: green, width: 30, height: 30),
  ),
)
Copy the code

The red Container resizes itself to the size of the child node, but it takes into account its spacing. So it’s going to be 30 times 30 plus the distance of 20 around it, and it’s because of that distance that the red is visible.

Similar to the previous example, the green Container is still 30 by 30.

Example 9

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

You might think that containers range from 70 to 150 pixels, but that’s not true. ConstrainedBox simply imposes additional constraints on the constraints passed by the parent layout.

Here, the screen forces ConstrainedBox’s size to be the exact screen size, so it tells its Container to be the screen size as well, thus ignoring the constraints parameter.

Example 10

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

Center now makes ConstrainedBox within the screen size range. ConstrainedBox imposes additional constraints on its children.

The Container has to be in the range of 70 to 150 pixels, it wants 10 pixels, so it can only be 70 pixels.

Example 11

Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      minWidth: 70,
      minHeight: 70,
      maxWidth: 150,
      maxHeight: 150,
    ),
    child: Container(color: red, width: 1000, height: 1000),),)Copy the code

Similarly, Center makes ConstrainedBox any size within the screen size range. ConstrainedBox imposes additional constraints on its children with constraints.

The Container must be in the range of 70 to 150 pixels, and it wants 1000 pixels, so its size is 150.

Example 12

Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      minWidth: 70,
      minHeight: 70,
      maxWidth: 150,
      maxHeight: 150,
    ),
    child: Container(color: red, width: 100, height: 100),),)Copy the code

Similarly, Center makes ConstrainedBox any size within the screen size range. ConstrainedBox imposes additional constraints on its children with constraints.

The Container has to be in the 70 to 150 pixel range, and the 100 it wants is exactly in the range, so its size is 100.

Example 13

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

The screen forces UnconstrainedBox to be the screen size, but UnconstrainedBox can have its child Container be any size that the child node wants.

So it’s 20 times 50.

Example 14

UnconstrainedBox(
  child: Container(color: red, width: 4000, height: 50),Copy the code

The screen forces UnconstrainedBox’s size to be the screen size, and UnconstrainedBox can make its child Container any size that the child node wants.

But in this example, the 4000 pixels that UnconstrainedBox’s child Container wants is too large, so UnconstrainedBox displays an “overflow Warning” warning.

Example 15

OverflowBox(minWidth: 0.0, minHeight: 0.0, maxWidth: double. Infinity, maxHeight: double. Infinity, child: Container(color: red, width: 4000, height: 50), )Copy the code

The screen enforces the Size of the OverflowBox to be the screen size, and the OverflowBox can make its child containers any size the child wants.

OverflowBox is similar to UnconstrainedBox except that it does not display the warning when the child node is too large.

In this example, the Container is 4000 pixels wide, which is out of the OverflowBox’s range, but the OverflowBox will do its best to display the content without warning.

Example 16

UnconstrainedBox(
  child: Container(color: Colors.red, width: double.infinity, height: 100),Copy the code

There are no components rendered, and you see error messages on the console.

UnconstrainedBox makes its child Container the size that any child wants. But the size of Container is infinite.

A Flutter does not render an infinite size, so it throws forces with BoxConstraints an infinite width. Abnormal information

Example 17

UnconstrainedBox(
  child: LimitedBox(
    maxWidth: 100,
    child: Container(
      color: Colors.red,
      width: double.infinity,
      height: 100,),),)Copy the code

You won’t see an error message here, because the LimitedBox component is given in UnconstrainedBox. It passes a maximum width of 100 pixels to the child nodes.

If you replace the UnconstrainedBox with the Center component, limit information is no longer applied to LimitedBox, because limit information is applied only if the constraint is infinite, and the width of the Container can exceed the given 100-pixel limit.

This explains the difference between LimitedBox and ConstrainedBox.

Example 18

const FittedBox(
  child: Text('Some Example Text.'),Copy the code

Screen force FittedBox size is screen size. Text also has some natural width, or natural width, depending on the size of the Text, the font size, and so on.

The FittedBox makes the Text any size it wants, but after the Text tells the FittedBox its size, the FittedBox scales the Text to cover the entire available width.

Example 19

const Center(
  child: FittedBox(
    child: Text('Some Example Text.'),),)Copy the code

What happens when you put the FittedBox in the Center? Center makes the FittedBox any size on the screen.

FittedBox determines its size to be the size of Text and makes Text the size that Text wants. Because FittedBox and Text are the same size, no scaling occurs.

Example 20

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.'),),)Copy the code

But what if the FittedBox child Text inside the Center component is wider than the screen?

FittedBox determines that it is the size of Text, but it is larger than the screen. It then sets the size to the screen size and adjusts the size of Text, which is also the screen width.

Example 21

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.'),Copy the code

However, if you remove the FittedBox, the width of the Text component is the width of the screen, and the Text component will wrap, so it will fit the screen size, and no errors will be reported.

Example 22

FittedBox(
  child: Container(
    height: 20.0,
    width: double.infinity,
    color: Colors.red,
  ),
)
Copy the code

FittedBox can only scale components that are not infinite; otherwise, it won’t render anything and you’ll see error messages in the console.

Example 23

Row(
  children: [
    Container(color: red, child: const Text('Hello!', style: big)),
    Container(color: green, child: const Text('Goodbye!', style: big)),
  ],
)
Copy the code

The size of the screen force Row component is the screen size.

Just like UnconstrainedBox, Row does not add constraints to its children, but makes them the size they want. The Row component then places the children side by side, leaving the remaining space empty.

Example 24

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)), ], )Copy the code

Because a Row does not impose any constraints on its children, the children may be too large to fit the width of the Row. In this example 🌰, just like UnconstrainedBox, Row appears with an “overflow warning” warning.

Example 25

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)), ], )Copy the code

When a Row’s child is wrapped in an Expanded widget, the Row won’t let this child define its own width anymore. If a Row’s children are wrapped in Expanded components, the Row does not let the children define their own dimensions.

Instead, Row defines Expanded widths based on other child nodes, so that Expanded forces the wrapped child node to be the same width as Expanded’s. That is, as long as Expanded is used, the original width of the wrapped child node is irrelevant.

Example 26

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, ), ), ), ], )Copy the code

If the Row’s children are wrapped in Expanded, the size of each Expanded depends on its ‘flex’ parameter. Only then does Expanded force the wrapped child node to be the same width as Expanded’s, that is, Expanded ignores the child node’s original width.

Example 27

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, ), ), ), ], )Copy the code

The difference between Flexible and Expanded is that the width of Flexible is greater than or equal to the width of the wrapped child node, whereas Expanded enforces the child node to be the width of Expanded. The similarity is that both ignore the width of the child node.

Example 28

Scaffold(
  body: Container(
    color: blue,
    child: Column(
      children: const [
        Text('Hello! '),
        Text('Goodbye! '(), [(), (), (Copy the code

The screen force Scaffold size is the screen size. As a result, that Scaffold fills the screen. Scaffold tells the Container that it can be any screen size.

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.

When a Widget tells its child nodes that it can be smaller than a certain size. Let’s say the Widget provides loose constraints on the child nodes.

Example 29

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

If you want the Scaffold’s children to be the same size as the Scaffold itself, you can wrap the children with SizedBox. Expand.

When a Widget tells its child nodes, the child nodes must be the exact size. Let’s say the Widget puts a tight constraint on its child nodes.

Tight vs. loose constraints

We often hear about tight or loose constraints, so what do they mean?

Tight constraints provide one possibility: precise dimensions. In other words, under tight constraints, the maximum width and height is equal to their minimum. In the source for Flutter, you can find a constructor for BoxConstraints:

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

Let’s look at Example 2 above. We mentioned that the screen forces the red Container to be the size of the screen. This is done by tight constraints, where the maximum and minimum values are tight constraints of the screen size.

The LOOSE constraint, on the other hand, simply sets the maximum width and height, and the child nodes only need to be larger than the maximum. That is, the minimum width and height of loose constraint is zero:

BoxConstraints.loose(Size size)
   : minWidth = 0.0,
     maxWidth = size.width,
     minHeight = 0.0,
     maxHeight = size.height;
Copy the code

Taking a look at Example 3 above, Center keeps the red Container no larger than the screen size by passing the loose constraint to the Container. Finally, Center gets the Tight constraint from the parent, turns it loose, and passes the transformed constraint to the child.

Understand the layout rules for specific widgets

Knowing general layout rules is essential, but stopping there is not enough.

Each Widget has a high degree of freedom when applying common rules. So you can’t tell what a Widget does just by looking at its name.

If it’s just a guess, it’s probably wrong. Only by reading the source code and documentation can you know exactly what the rules are behind the Widget.

The source code of the layout is generally more complex, so reading the document is more acceptable a little 🤏. But if you decide to learn the source of the layout, you can use the IDE’s navigation to locate the source.

Here’s an example:

  • How far to find Column. To do this, use the Command +B (macOS) or Control +B (Windows/Linux) shortcuts in Android Studio or IntelliJ. Dart file, because Column inherits from Flex, and continues to trace the Flex source code (also in basic.dart).

  • Find the createRenderObject() method. As you can see, the method returns RenderFlex. That’s the render object behind Column. Now trace back to the RenderFlex source in the flex.dart file.

  • Find the performLayout() method. This method is the layout rule method for Column.