A flexible layout is a way to distribute the available space among multiple widgets.
We already know how to create nice boxes with Container. But an app isn’t just a box floating in nothingness. It’s many boxes: some on top of others, some next to each other…
To organize elements on the screen, Flutter uses a system very similar to CSS Flexbox. The undisputed kings of this system are Row and Column.
Mastering these two widgets and their alignment axes is 80% of the success when laying out a screen.
Row and Column
The difference is obvious:
- Row: Places widgets horizontally (next to each other).
- Column: Places widgets vertically (one below the other).
Unlike Container, which only accepts a single child (child), these layouts accept a list of children (children).
Column(
children: [
Text("Element 1"),
Text("Element 2"),
BotonBonito(),
],
)
Main axis and cross axis
This is where everyone gets confused at first. To align objects, we don’t use “Left/Right” or “Up/Down”. We use Main Axis and Cross Axis.
The orientation of these axes changes depending on whether you are in a Row or a Column.
- MainAxis: It is the Vertical axis (↕). Defines how children are distributed from top to bottom.
- CrossAxis: It is the Horizontal axis (↔). Defines how children are aligned left/right.
- MainAxis: It is the Horizontal axis (↔). Defines how children are distributed from left to right.
- CrossAxis: It is the Vertical axis (↕). Defines how children are aligned up/down.
Aligning elements
To control this, we use the mainAxisAlignment and crossAxisAlignment properties.
Defines how children are distributed in the available space of the main axis.
start: At the beginning (Top in Col, Left in Row).center: At the center.end: At the end.spaceBetween: Puts all free space between the elements.spaceAround: Puts space around.
Column(
mainAxisAlignment: MainAxisAlignment.center, // Vertically centered
children: [ ... ],
)
Defines how children are placed on the opposite axis.
start: Stuck to the start (Left in Col, Top in Row).center: Centered.stretch: Stretch to occupy as much width (or height) as possible.
Column(
crossAxisAlignment: CrossAxisAlignment.stretch, // Occupies all the width
children: [ ... ],
)
A Column by default tries to occupy all possible vertical height (MainAxisSize.max), but only the width needed by its children.
The overflow problem
It’s surely happened to you. You put many texts in a Row and suddenly… BAM! A yellow and black caution stripe appears and an error in the console: “RenderFlex overflowed by X pixels”.
This happens because the children sum up to more width than the screen has. Row and Column are “inflexible” layouts by default; they don’t know how to shrink their children automatically.
A common solution is Expanded, although depending on the design, using Flexible, allowing line breaks, wrapping the content, or making it scrollable might also be convenient.
Expanded and Flexible
Expanded is a widget that tells a child of a Row/Column:
“Hey, stop having a fixed size. Expand and take up all the remaining free space”.
Row(
children: [
Icon(Icons.person), // Fixed size (the icon's)
// The text will occupy ALL the remaining space, pushing the rest
Expanded(
child: Text("This is a very long text that now fits because it wraps..."),
),
Icon(Icons.arrow_forward), // Fixed size
],
)
Proportions with flex
If you have several Expanded widgets, they will distribute the space equally. But you can use the flex property to assign priorities.
Row(
children: [
Expanded(flex: 2, child: Container(color: Colors.red)), // Occupies 2/3
Expanded(flex: 1, child: Container(color: Colors.blue)), // Occupies 1/3
],
)
SizedBox as a separator
When you just need to separate two elements, a SizedBox clearly expresses the size of the gap.
Column(
children: [
Text("Title"),
SizedBox(height: 20), // A vertical gap of 20px
Text("Subtitle"),
],
)
Reading SizedBox(height: 20) is much more readable than reading a nested Container(margin: ...).