14 Classic Layout How to Define Child Controls Placement in Container

14 Classic Layout How to Define Child Controls Placement in Container #

Hello, I’m Chen Hang.

In the previous two articles, we learned about the basic elements for building views in Flutter: text, images, and buttons. We also explored ListView for displaying a continuous list of view elements, and CustomScrollView for handling multiple nested scrollable views.

In Flutter, a complete interface is usually built by stacking these small, single-purpose basic widget elements according to specific layout rules. Today, I will take you through the process of building a beautiful layout in Flutter. We will learn about the layout rules we need to understand and the differences between these rules and similar concepts in other platforms. I hope that this design will help you efficiently learn Flutter’s layout rules based on your existing experience.

We already know that in Flutter, everything is a widget, and layouts are no exception. However, unlike basic widget elements, layout widgets do not directly present visual content. Instead, they serve as containers for other child widgets.

These layout widgets contain one or more child widgets and provide different layout methods to arrange the child widgets, allowing for alignment, nesting, overlaying, and scaling of the child widgets. Our task is to use customized parameters to place the child widgets inside the layout widget according to our own layout rules, ultimately creating a beautiful layout.

Flutter provides 31 types of layout widgets, categorizing layout widgets in great detail. Some similar visual effects can be achieved using multiple layout widgets, thus Flutter has more layout options compared to the native Android and iOS platforms. For example, Android has only five layout types: FrameLayout, LinearLayout, RelativeLayout, GridLayout, and TableLayout. iOS has even fewer, with only Frame layout and Auto Layout.

To help you establish an understanding of layout widgets, understand the layout characteristics and usage of basic layout widgets, and quickly get started with Flutter development, I have selected several commonly used and representative layout widgets in this article. These include single-child layout, multiple-child layout, and stacked widget layout, which I will introduce to you.

By mastering these typical widgets, you will have a good grasp of all the layout methods needed to build a visually appealing app interface. Next, let’s start with the single-child layout.

Single Widget Layout: Container, Padding, and Center #

Single widget layout container is relatively simple and is generally used to wrap the only child widget for styling purposes, such as limiting its size, adding background color, padding, rotation transformation, etc. This type of layout widget includes Container, Padding, and Center.

Container is a widget that allows adding other widgets inside it, and it is also a common concept in UI frameworks.

In Flutter, the Container can exist as a standalone widget (for example, setting background color and width and height), or it can exist as a parent widget for other widgets: the Container defines how the child widget is laid out and displayed during the layout process. Unlike other frameworks, Flutter’s Container can only contain one child widget.

Therefore, for layout scenarios with multiple child widgets, we usually handle it like this: first, use a root widget to wrap these child widgets, then put this root widget in the Container, and then the Container sets its basic properties and style attributes, such as alignment, padding, etc.

Next, let me show you how to define a Container through an example.

In this example, I will wrap a long text in a Container with a red background, rounded border, and fixed width and height. I will also set the external margin (margin from its parent widget) and internal padding (distance from its child widget): dart Container( child: Text('Container is a common concept in UI frameworks, and Flutter is no exception.'), padding: EdgeInsets.all(18.0), // internal padding margin: EdgeInsets.all(44.0), // external margin width: 180.0, height: 240.0, alignment: Alignment.center, // center align the child widget decoration: BoxDecoration( // Container style color: Colors.red, // background color borderRadius: BorderRadius.circular(10.0), // rounded border ), )

Container Example

Figure 1 Container Example

If we only need to set the spacing between child widgets, we can use another single widget container, Padding, to fill the content: dart Padding( padding: EdgeInsets.all(44.0), child: Text('Container is a common concept in UI frameworks, and Flutter is no exception.'), );

Padding Example

Figure 2 Padding Example

When setting content spacing, we can use different constructors of EdgeInsets to specify different padding methods for the four directions, such as using the same padding value for all directions, only setting the left padding, or symmetric spacing. If you want to learn more about this part, you can refer to this API documentation.

Next, let’s take a look at another commonly used container in single widget layout containers, Center. As the name suggests, Center horizontally and vertically centers its child widget.

For example, we can wrap a Text widget in Center to achieve centered display: dart Scaffold( body: Center(child: Text("Hello")), );

Center Example

Figure 3 Center Example

It is important to note that in order to achieve centered layout, the space occupied by the Center must be larger than its child widget. This is obvious: if the Center is the same size as its child widget, it naturally does not need to be centered, and there is no space to center it. Therefore, Center is usually used in combination with Container.

Now, let’s combine Container and Center to look at the specific usage of Center.

Container(
  child: Center(child: Text('Container is a common concept in UI frameworks, and Flutter is no exception.')),
  padding: EdgeInsets.all(18.0), // internal padding
  margin: EdgeInsets.all(44.0), // external margin
  width: 180.0,
  height: 240.0,
  decoration: BoxDecoration( // Container style
    color: Colors.red, // background color
    borderRadius: BorderRadius.circular(10.0), // rounded border
  ),
);

We can see that we achieve the effect of alignment: Alignment.center in Container by using the Center container.

In fact, to achieve this effect, both the Container container and the Center container rely on the same underlying container, Align, to align the child widget. The usage of Align is also relatively simple. If you want to learn more about it, you can refer to the official documentation. I won’t go into too much detail here.

Next, let’s take a look at the three ways to layout multiple child widgets: Row, Column, and Expanded.

Multi-child widget layout: Row, Column, and Expanded #

For layout classes that have multiple child widgets, there are only two abstract rules for how they should be laid out: how to lay them out horizontally and how to lay them out vertically.

Just like Android’s LinearLayout and the Flex layout in front-end development, Flutter also has similar concepts, namely Row, which arranges child widgets in a horizontal line, Column, which arranges child widgets in a vertical line, and Expanded, which is responsible for distributing the remaining space of these child widgets in the layout direction (row/column).

The usage of Row and Column is very simple, we just need to add the child widgets in the children array. In the code below, we add four containers with different colors, widths, and heights to Row and Column:

// Row example
Row(
  children: <Widget>[
    Container(color: Colors.yellow, width: 60, height: 80,),
    Container(color: Colors.red, width: 100, height: 180,),
    Container(color: Colors.black, width: 60, height: 80,),
    Container(color: Colors.green, width: 60, height: 80,),
  ],
);

// Column example
Column(
  children: <Widget>[
    Container(color: Colors.yellow, width: 60, height: 80,),
    Container(color: Colors.red, width: 100, height: 180,),
    Container(color: Colors.black, width: 60, height: 80,),
    Container(color: Colors.green, width: 60, height: 80,),
  ],
);

Row example (a) Row example

Column example (b) Column example

Figure 4: Examples of Row and Column

As we can see, when using just Row and Column widgets, if the size of the child widgets is small, the container cannot be filled, which makes the visual style less appealing. For this kind of scenario, we can use the Expanded widget to specify how to fill the remaining space of the container.

For example, if we want the green container and yellow container in the Row (or Column) to evenly share the remaining space, we can set their flex factors flex to 1. These two Expanded widgets will divide the remaining space horizontally (or vertically) according to their flex ratio (1:1):

Row(
  children: <Widget>[
    Expanded(flex: 1, child: Container(color: Colors.yellow, height: 60)), // the width is distributed by Expanded due to the flex factor being set to 1
    Container(color: Colors.red, width: 100, height: 180,),
    Container(color: Colors.black, width: 60, height: 80,),
    Expanded(flex: 1, child: Container(color: Colors.green,height: 60),)/ the width is distributed by Expanded due to the flex factor being set to 1
  ],
);

Expanded widget example Figure 5: Example of Expanded widget

For Row and Column, Flutter provides alignment behaviors based on the coordinate axes, where the main axis is used to arrange child widgets in sequence, and the cross axis is the direction perpendicular to the main axis.

Row and Column coordinate axes

Figure 6: The main axis and cross axis of Row and Column

We can set the alignment rules for child widgets in these two directions, namely MainAxisAlignment and CrossAxisAlignment, according to the main axis and cross axis. For example, start in the main axis direction means left alignment, center means horizontal center alignment, end means right alignment, and spaceEvenly means alignment with fixed spacing. For the cross axis direction, start means top alignment, center means vertical center alignment, and end means bottom alignment.

The following image shows the effect of setting different alignment rules in Row:

Row main axis alignment

Figure 7: Main axis alignment in Row

Row cross axis alignment

Figure 8: Cross axis alignment in Row

The alignment rules for Column are similar, so I won’t go into detail.

It should be noted that for the main axis, Flutter defaults to letting the parent container determine its length, i.e. as big as possible, similar to match_parent in Android.

In the example above, the width of the Row is the screen width, and the height of the Column is the screen height. The length of the main axis is greater than the total length of all child widgets, which means that the space in the main axis direction is larger than the child widgets, and this is why we can set the layout effect of child widgets through the alignment of the main axis.

If we want the container to completely match the child widgets in the main axis, we can set the mainAxisSize parameter of Row to MainAxisSize.min, which allows all child widgets to determine the length of the container in the main axis direction, i.e., the length is as small as possible, similar to wrap_content in Android:

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly, // this line of code has no effect because the container is the same width as the child widgets
  mainAxisSize: MainAxisSize.min, // make the width of the container the same as the width of all child widgets
  children: <Widget>[
    Container(color: Colors.yellow, width: 60, height: 80,),
    Container(color: Colors.red, width: 100, height: 180,),
    Container(color: Colors.black, width: 60, height: 80,),
    Container(color: Colors.green, width: 60, height: 80,),
  ],
)

Row main axis size Figure 9: Main axis size of Row

As we can see, after setting the main axis size to MainAxisSize.min, the width of the Row becomes the same as its child widgets, so the alignment of the main axis no longer has any effect.

Stacked Widget Layout: Stack and Positioned #

Sometimes, we need to stack one widget on top of another, such as placing text on top of an image or placing a button in a specific area of an image. In these cases, we use the stack layout container, Stack.

The Stack container is similar to absolute positioning in front-end development and FrameLayout in Android. It allows overlapping of child widgets and allows them to be positioned based on the corners of the parent container.

Stack provides the container for stacked layouts, while Positioned provides the ability to set the position of child widgets. Let’s take a look at the usage of Stack and Positioned through an example.

In this example, I first placed a yellow canvas of size 300_300 in the Stack, and then placed a green widget of size 50_50 at position (18, 18), and a text widget at position (18, 70).

Stack(
  children: <Widget>[
    Container(color: Colors.yellow, width: 300, height: 300), // yellow container
    Positioned(
      left: 18.0,
      top: 18.0,
      child: Container(color: Colors.green, width: 50, height: 50), // green widget overlaid on yellow container
    ),
    Positioned(
      left: 18.0,
      top: 70.0,
      child: Text("Stack provides the container for stacked layouts"), // text overlaid on yellow container
    )
  ],
)

Try running the code, and you will see that the three child widgets are stacked together according to our specified rules.

Figure 10: Example of Stack and Positioned containers

The Stack widget allows its child widgets to be stacked in the order they are created, and the Positioned widget is used to control the placement of these child widgets. It’s important to note that the Positioned widget can only be used within a Stack container and will throw an error if used in other containers.

Summary #

Flutter’s layout containers are powerful and rich, allowing you to quickly encapsulate basic visual elements into widgets. Today, I have selected several representative and commonly used layout widgets in Flutter to introduce the layout concepts needed to build a beautiful app interface.

Now, let’s briefly review the content we covered today to deepen our understanding and memory:

First, we learned about the single-child containers, Container, Padding, and Center. Among them, the Container provides basic properties such as spacing and background styles, and it also provides customization capabilities for the placement and display styles of the child widgets. On the other hand, Padding and Center are concise in functionality, focusing on alignment and centering.

Next, we delved into the multi-child layout with Row and Column widgets, including the alignment rules between child widgets, the expansion rules of the container itself, and how to use the Expanded widget to utilize the remaining space within a container.

Finally, we learned about the stack layout, Stack, and the Positioned container used in conjunction with it for positioning child widgets. With these widgets, you can achieve the layout effect of stacking multiple widgets.

With the knowledge gained from today’s article, I believe you now have sufficient knowledge to build app interfaces. In the next article, I will guide you through some practical examples to show you how to create attractive interfaces using these basic widgets and layout rules in Flutter.

Thinking Questions #

Finally, I would like to leave you with a thinking question.

How are the sizes of rows and columns determined? And what happens when they are nested?

Please feel free to share your thoughts in the comments section, and I will be waiting for you in the next article! Thank you for listening, and feel free to share this article with more friends to read together.