Master Flutter Layout: Container, Column, Row & Stack | (Ch. 7)
Welcome to Chapter 7! In the last chapter, we took a deep dive into
the *theory* of Stateless vs. Stateful
widgets. Before that, in Project 1, we built a digital business card
and got our first taste of layout by using Column,
Center, and SizedBox.
We were a bit like chefs following a recipe—we knew the steps, but
maybe not *why* each step worked. Why did
mainAxisAlignment center things vertically? What did
Card really do?
This chapter is where we become layout masters. The entire Flutter framework is built on a simple, powerful idea: "everything is a widget." But the *skeleton* that holds those widgets together is layout. If you can master layout, you can build *any* 2D user interface you can dream of.
We're going to focus on the "Big Four" of layout:
- Container: The basic "box" or "div" for styling.
- Column: For arranging widgets vertically.
- Row: For arranging widgets horizontally.
- Stack: For "layering" widgets on top of each other (like pancakes or Photoshop layers).
This is one of the most important chapters in the entire course. Take your time, and let's get building.
📖 Chapter 7: Table of Contents
1. `Container`: The Magic Box
The Container widget is the most fundamental and
versatile layout widget in Flutter. If you come from web
development, it's very similar to a <div>, but on
steroids.
A Container, by itself, is just an invisible, empty box.
Its true power comes from its properties. It can hold a single
child widget, but it can also be used as a "spacer"
or a "decorator" all by itself.
Sizing (width & height)
By default, a Container with no child and no size will
try to be as big as possible (it will fill its
parent). If it has a child, it will shrink to fit
that child.
We can give it a specific size:
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
color: Colors.blue,
width: 200.0,
height: 100.0,
child: Text('I am in a box!'),
),
),
);
}
This gives us a 200x100 blue box with text in it. Simple!
Spacing (padding & margin)
This is a concept that confuses many beginners, but it's simple with the right analogy.
-
padding: Space inside the container, between the border and thechild. Think of it as "padding" a box with cotton to protect what's inside. -
margin: Space outside the container, between its border and the *next* widget. Think of it as "social distancing" from other widgets.
Let's see an example:
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
body: Center(
child: Container(
color: Colors.blue,
width: 300.0,
height: 300.0,
// 10 pixels of "social distancing" on all sides
margin: EdgeInsets.all(10.0),
// 40 pixels of "cotton padding" on all sides
padding: EdgeInsets.all(40.0),
child: Container(
color: Colors.red,
child: Text('Hello'),
),
),
),
);
}
In this code, the blue box will have 10 pixels of grey space around it (margin). The red box *inside* it will have 40 pixels of blue space around it (padding).
Styling (decoration & the color conflict)
This is the "gotcha" that hits every new Flutter dev. You
can set a color directly, like we did
above.
But what if you want rounded corners? Or a border? Or a shadow?
For that, you need the decoration property, which
takes a BoxDecoration.
CRITICAL ERROR: You cannot provide *both* acolorproperty and adecorationproperty. Thecolorproperty is just a shortcut fordecoration: BoxDecoration(color: ...). If you provide both, Flutter throws an error.
The Fix: If you need *any* decoration, you
must put the color *inside* the
BoxDecoration.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
width: 300.0,
height: 300.0,
// This is how you do complex styling
decoration: BoxDecoration(
// 1. The color goes INSIDE
color: Colors.lightGreen,
// 2. Add a border
border: Border.all(
color: Colors.black,
width: 3.0,
),
// 3. Add rounded corners
borderRadius: BorderRadius.circular(12.0),
// 4. Add a drop shadow
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: Offset(0, 3), // changes position of shadow
),
],
),
child: Center(child: Text('I am a fancy box!')),
),
),
);
}
Alignment
What if your Container is big, but its
child is small? Where does the child go? By
default, it goes in the top-left.
We can use the alignment property to change this.
child: Container(
color: Colors.blue,
width: 300.0,
height: 300.0,
// Tell the container to align its child
alignment: Alignment.bottomRight,
child: Text('I am small!'),
),
Now, the text "I am small!" will be in the bottom-right corner of the blue box.
2. The "Golden Rule" of Layout: Main Axis vs. Cross Axis
I cannot overstate this. If you understand this one concept, you
will understand Column and Row
instantly.
Column and Row are both "flex" widgets.
They arrange a List of children along an "axis."
- The Main Axis is the direction the widget *scrolls* in.
- The Cross Axis is the other direction.
For a Column:
- It arranges children vertically.
- So, its Main Axis is Vertical (Y-axis).
- Its Cross Axis is Horizontal (X-axis).
For a Row:
- It arranges children horizontally.
- So, its Main Axis is Horizontal (X-axis).
- Its Cross Axis is Vertical (Y-axis).
Why does this matter? Because the two main layout properties are
mainAxisAlignment and
crossAxisAlignment.
This is brilliant. Instead of "verticalAlignment" or "horizontalAlignment," Flutter uses these two names. Now you know exactly what they mean:
-
mainAxisAlignmentin aColumn= Vertical alignment. -
crossAxisAlignmentin aColumn= Horizontal alignment. -
mainAxisAlignmentin aRow= Horizontal alignment. -
crossAxisAlignmentin aRow= Vertical alignment.
3. `Column`: Stacking Vertically
This is our workhorse. We used it in Project 1. It takes a
List<Widget> called children and
arranges them from top to bottom.
MainAxisAlignment (Vertical Alignment)
This controls how the children are spaced out along the vertical (main) axis.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
// Let's try different values here!
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(color: Colors.red, width: 100, height: 100),
Container(color: Colors.green, width: 100, height: 100),
Container(color: Colors.blue, width: 100, height: 100),
],
),
);
}
-
.start: (Default) Puts all children at the top. -
.center: Puts all children in a group in the middle. -
.end: Puts all children at the bottom. -
.spaceBetween: Puts space between the children. The first and last child are flush with the edges. -
.spaceAround: Puts space "around" each child. (Half space on the ends). -
.spaceEvenly: Puts equal space between all children *and* the ends.
CrossAxisAlignment (Horizontal Alignment)
This controls how the children are aligned on the horizontal (cross) axis.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
// How are the children aligned horizontally?
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// Notice the different widths
Container(color: Colors.red, width: 100, height: 100),
Container(color: Colors.green, width: 300, height: 100),
Container(color: Colors.blue, width: 150, height: 100),
],
),
);
}
-
.start: (Default) Aligns all children to the left. -
.center: Aligns all children to the center. (This is what we did in Project 1!). -
.end: Aligns all children to the right. -
.stretch: This is a powerful one. It forces all children to be as wide as theColumnitself.
4. `Row`: Arranging Horizontally
A Row is *exactly* like a Column, but
the axes are flipped. It arranges its children from
left to right.
Main Axis: Horizontal
Cross Axis: Vertical
The Overflow Error & The `Expanded` Widget
Row has one *huge* problem that Column
doesn't. A phone screen is very tall, so you can add many
Containers to a Column.
But a phone screen is not very wide. What
happens if you put too much stuff in a Row?
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
children: <Widget>[
Container(color: Colors.red, width: 200, height: 100),
Container(color: Colors.green, width: 200, height: 100),
// Uh oh! 200 + 200 is > screen width!
],
),
),
);
}
You'll get the dreaded "Overflow" error: a yellow and black striped box. This is Flutter's way of screaming, "These widgets don't fit!"
The solution is the Expanded widget.
Expanded is a widget that tells its child, "I know
you want a certain size, but just shut up and
take up all the remaining empty space in the
Row or Column."
It *must* be a child of a Row or Column.
Let's fix our error:
child: Row(
children: <Widget>[
// This first Container is a fixed size
Container(color: Colors.red, width: 100, height: 100),
// This Expanded widget will take up ALL other space
Expanded(
child: Container(color: Colors.green, height: 100),
),
Container(color: Colors.blue, width: 100, height: 100),
],
),
Now, the red and blue boxes will be 100px wide, and the green box will *expand* to fill the entire space in between them. No overflow!
You can also use Expanded with a flex
factor to divide the space.
child: Row(
children: <Widget>[
Expanded(
flex: 2, // Take 2 "shares" of the space
child: Container(color: Colors.red, height: 100),
),
Expanded(
flex: 1, // Take 1 "share" of the space
child: Container(color: Colors.blue, height: 100),
),
],
),
In this example, the red container will be *twice as wide* as the blue one.
MainAxisAlignment (Horizontal Alignment)
This controls how children are spaced on the horizontal axis.
-
.start: (Default) Puts all children at the left. -
.center: Puts all children in a group in the middle. -
.end: Puts all children at the right. -
.spaceBetween: Puts space between the children.
CrossAxisAlignment (Vertical Alignment)
This controls how children are aligned on the
vertical axis. This is very useful if your
Row has a fixed height.
child: Container(
height: 300.0, // Give the Row a fixed height
color: Colors.grey,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
// How are children aligned vertically?
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(color: Colors.red, width: 50, height: 50),
Container(color: Colors.green, width: 50, height: 150),
Container(color: Colors.blue, width: 50, height: 80),
],
),
),
-
.start: Aligns all children to the top of theRow. -
.center: (Default) Aligns all children to the vertical center. -
.end: Aligns all children to the bottom.
5. `Stack`: Layering Widgets
Column and Row are for 2D layout. What
if you want to lay widgets on top of each other? Like text *on*
an image?
For this, we use a Stack.
A Stack is like a stack of pancakes. It also takes a
children list. The first widget in
the list is the bottom layer. The
last widget in the list is the
top layer.
By default, all children in a Stack are aligned to
the top-left.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Stack(
children: <Widget>[
// 1. Bottom Layer
Container(
color: Colors.green,
width: 300,
height: 300,
),
// 2. Middle Layer
Container(
color: Colors.yellow,
width: 200,
height: 200,
),
// 3. Top Layer
Text('I am on top!'),
],
),
),
);
}
This will show a green box, with a smaller yellow box on top of it, with the text on top of that. All are stuck in the top-left corner *of the Stack*.
To fix this, we can use Stack's
alignment property (e.g.,
alignment: Alignment.center) to align all the
*non-positioned* children.
The `Positioned` Widget
What if you want total control? What if you want one widget in the top-left and another in the bottom-right?
For this, you wrap a child of a Stack in a
Positioned widget.
Positioned lets you "pin" a widget to any corner or
edge of the Stack.
child: Stack(
// Set a default alignment for non-Positioned children
alignment: Alignment.center,
children: <Widget>[
// 1. Bottom Layer (the background)
Container(
color: Colors.blue,
width: 300,
height: 300,
),
// 2. This text is not Positioned, so it uses
// the Stack's "Alignment.center"
Text('I am in the center'),
// 3. This widget is Positioned!
Positioned(
bottom: 10.0,
right: 10.0,
child: Container(
color: Colors.red,
width: 50,
height: 50,
),
),
// 4. This one is also Positioned
Positioned(
top: 10.0,
left: 10.0,
child: Text('I am in the corner'),
),
],
),
This is an incredibly powerful pattern. The most common use is
to have a Container with a background image as the
first child (bottom layer), and then use Positioned
widgets to place text or buttons on top of it.
Conclusion: You are Now a Layout Architect
This was a massive chapter. If your head is spinning, that's normal! This is the densest part of Flutter. But you now have the "golden rules."
You've learned:
-
Containeris your "magic box" for spacing (padding,margin) and styling (decoration). - The "Golden Rule": Main Axis (the direction of flow) and Cross Axis (the other one).
-
Columnstacks vertically.mainAxisAlignmentis vertical,crossAxisAlignmentis horizontal. -
Rowstacks horizontally.mainAxisAlignmentis horizontal,crossAxisAlignmentis vertical. -
Rows overflow! You must useExpandedto tell children how to share space and prevent errors. -
Stacklayers widgets. The first child is the bottom, the last child is the top. -
Positionedgives you fine-grained control over where children live inside aStack.
Here is the secret: There are no other "magic" layout widgets. 99% of all UIs in Flutter are just clever, nested combinations of these four widgets.
A Twitter UI? It's a Column. Each item in the
Column is a Row. That
Row contains a CircleAvatar (for the
profile pic) and another Column (for the name and
tweet text). It's just nesting these simple blocks.
In the next chapter, we'll move on to the next
set of "essential" UI widgets: Text,
Icon, and Image. We'll learn how to
style text deeply and, most importantly, how to load images from
our app's "assets" folder and from the internet.
Go play! Try to build a simple social media post. You have all the tools you need.
Comments
Post a Comment