#flutter /
Flutter Layout Practice: Correct Usage of Row, Column, and Stack
Deep dive into Flutter's three core layout components Row, Column, and Stack, their core properties and practical usage, mastering techniques for building flexible responsive UIs.
Goal
Flutter's layout system is based on Widget composition. Understanding Row, Column, and Stack is fundamental to building UIs. This article deeply explains these three components' core properties, common layout scenarios, and practical techniques to help developers flexibly build various complex UI layouts.
Background
Flutter Layout Model
Flutter uses a box model for layout, where each Widget occupies a rectangular area:
┌─────────────────────────────────┐
│ Parent Widget │
│ ┌──────────┐ ┌──────────┐ │
│ │ Child 1 │ │ Child 2 │ │
│ └──────────┘ └──────────┘ │
│ ┌──────────────────────────┐ │
│ │ Child 3 │ │
│ └──────────────────────────┘ │
└─────────────────────────────────┘
Layout Component Categories
- Row/Column: Linear layout (horizontal/vertical)
- Flex: Flexible layout (parent of Row/Column)
- Stack: Stacking layout
- Wrap: Flow layout
- GridView: Grid layout
Row Horizontal Layout
Basic Usage
Row(
children: [
Icon(Icons.star),
Icon(Icons.star),
Icon(Icons.star),
],
)
MainAxisAlignment (Main Axis Alignment)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(Icons.star),
Icon(Icons.star),
Icon(Icons.star),
],
)
// MainAxisAlignment enum values
// start: Left-aligned
// end: Right-aligned
// center: Centered
// spaceBetween: Space between, evenly distributed
// spaceAround: Space around each element
// spaceEvenly: All spaces equal
CrossAxisAlignment (Cross Axis Alignment)
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.star, size: 32),
Icon(Icons.star, size: 16),
Icon(Icons.star, size: 24),
],
)
// CrossAxisAlignment enum values
// start: Top-aligned
// end: Bottom-aligned
// center: Centered
// stretch: Stretch to fill
// baseline: Baseline aligned
Practice: Navigation Bar
class NavBar extends StatelessWidget {
final String title;
const NavBar({super.key, required this.title});
Widget build(BuildContext context) {
return Container(
height: 56,
padding: EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 4,
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Left: Back button
IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
// Center: Title
Text(
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
// Right: Action buttons
Row(
children: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.more_vert),
onPressed: () {},
),
],
),
],
),
);
}
}
Column Vertical Layout
Basic Usage
Column(
children: [
Text('Title'),
Text('Content'),
Text('Footer'),
],
)
Practice: Profile Card
class ProfileCard extends StatelessWidget {
final String name;
final String avatar;
final String bio;
const ProfileCard({
super.key,
required this.name,
required this.avatar,
required this.bio,
});
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 8,
),
],
),
child: Column(
children: [
// Avatar
CircleAvatar(
radius: 40,
backgroundImage: NetworkImage(avatar),
),
SizedBox(height: 12),
// Name
Text(
name,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
// Bio
Text(
bio,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
SizedBox(height: 16),
// Action buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildButton(Icons.message, 'Message'),
_buildButton(Icons.person_add, 'Follow'),
_buildButton(Icons.share, 'Share'),
],
),
],
),
);
}
Widget _buildButton(IconData icon, String label) {
return Column(
children: [
Icon(icon, color: Colors.blue),
SizedBox(height: 4),
Text(label, style: TextStyle(color: Colors.blue)),
],
);
}
}
Stack Stacking Layout
Basic Usage
Stack(
children: [
// Bottom layer: Background image
Image.network('https://example.com/bg.jpg'),
// Middle layer: Semi-transparent overlay
Container(color: Colors.black54),
// Top layer: Text content
Center(
child: Text(
'Title',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
],
)
Positioned Precise Positioning
Stack(
children: [
// Background
Container(
width: 200,
height: 200,
color: Colors.blue,
),
// Top-left corner
Positioned(
top: 10,
left: 10,
child: Icon(Icons.star, color: Colors.yellow),
),
// Bottom-right corner
Positioned(
bottom: 10,
right: 10,
child: Icon(Icons.favorite, color: Colors.red),
),
// Center
Positioned(
top: 0,
bottom: 0,
left: 0,
right: 0,
child: Center(
child: Text('Center', style: TextStyle(color: Colors.white)),
),
),
],
)
Practice: Product Card
class ProductCard extends StatelessWidget {
final String imageUrl;
final String name;
final double price;
final double? originalPrice;
final bool isNew;
const ProductCard({
super.key,
required this.imageUrl,
required this.name,
required this.price,
this.originalPrice,
this.isNew = false,
});
Widget build(BuildContext context) {
return Stack(
children: [
// Card body
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 8,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Product image
ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
child: Image.network(
imageUrl,
height: 150,
width: double.infinity,
fit: BoxFit.cover,
),
),
// Product info
Padding(
padding: EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 8),
Row(
children: [
Text(
'\$${price.toStringAsFixed(2)}',
style: TextStyle(
color: Colors.red,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
if (originalPrice != null) ...[
SizedBox(width: 8),
Text(
'\$${originalPrice!.toStringAsFixed(2)}',
style: TextStyle(
color: Colors.grey,
decoration: TextDecoration.lineThrough,
),
),
],
],
),
],
),
),
],
),
),
// New product label
if (isNew)
Positioned(
top: 8,
left: 8,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'New',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
),
// Shopping cart button
Positioned(
bottom: 70,
right: 8,
child: Container(
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
child: IconButton(
icon: Icon(Icons.add_shopping_cart, color: Colors.white, size: 20),
onPressed: () {},
),
),
),
],
);
}
}
Combined Layouts
Practice: Chat Interface
class ChatBubble extends StatelessWidget {
final String message;
final bool isMe;
final String avatar;
final DateTime time;
const ChatBubble({
super.key,
required this.message,
required this.isMe,
required this.avatar,
required this.time,
});
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Row(
mainAxisAlignment: isMe ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// Other person's avatar
if (!isMe) ...[
CircleAvatar(
radius: 16,
backgroundImage: NetworkImage(avatar),
),
SizedBox(width: 8),
],
// Message content
Flexible(
child: Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: isMe ? Colors.blue : Colors.grey[200],
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
bottomLeft: isMe ? Radius.circular(16) : Radius.circular(4),
bottomRight: isMe ? Radius.circular(4) : Radius.circular(16),
),
),
child: Text(
message,
style: TextStyle(
color: isMe ? Colors.white : Colors.black,
),
),
),
),
// My avatar
if (isMe) ...[
SizedBox(width: 8),
CircleAvatar(
radius: 16,
backgroundImage: NetworkImage(avatar),
),
],
],
),
);
}
}
Practice: Tab Layout
class TabBarDemo extends StatefulWidget {
const TabBarDemo({super.key});
State<TabBarDemo> createState() => _TabBarDemoState();
}
class _TabBarDemoState extends State<TabBarDemo>
with SingleTickerProviderStateMixin {
late TabController _tabController;
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
void dispose() {
_tabController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Tab Layout'),
bottom: TabBar(
controller: _tabController,
tabs: [
Tab(icon: Icon(Icons.home), text: 'Home'),
Tab(icon: Icon(Icons.search), text: 'Search'),
Tab(icon: Icon(Icons.person), text: 'Profile'),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
Center(child: Text('Home content')),
Center(child: Text('Search content')),
Center(child: Text('Profile content')),
],
),
);
}
}
Responsive Layout
LayoutBuilder
class ResponsiveLayout extends StatelessWidget {
final Widget mobile;
final Widget tablet;
final Widget desktop;
const ResponsiveLayout({
super.key,
required this.mobile,
required this.tablet,
required this.desktop,
});
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return mobile;
} else if (constraints.maxWidth < 1200) {
return tablet;
} else {
return desktop;
}
},
);
}
}
// Usage
ResponsiveLayout(
mobile: Column(children: [...]),
tablet: Row(children: [...]),
desktop: Row(children: [...]),
)
Conclusion
Flutter's layout system is flexible and powerful. Key takeaways:
- Row/Column: Foundation of linear layout, master main axis and cross axis alignment
- Stack: Stacking layout, suitable for UI scenarios requiring overlap
- Combined usage: Complex UIs are composed of simple layout components
- Responsive design: Use LayoutBuilder to adapt to different screen sizes
Once you master these three core layout components, you can build the vast majority of UI layouts. Remember Flutter's core philosophy: everything is a Widget, build complex UIs by composing small Widgets.