Building Complex UIs
Master Flutter's layout widgets to create responsive, production-grade interfaces.
Choosing the Right Layout Widget
| Widget | Direction | Use When |
|---|---|---|
Row | Horizontal | Arranging items side by side (toolbar, chips) |
Column | Vertical | Stacking items vertically (form, list) |
Stack | Overlay | Layering widgets (badges, overlays) |
Wrap | Flow | Tags, chips that flow to next line |
ListView | Scrollable | Long lists with lazy rendering |
GridView | Grid | Photo galleries, product grids |
💡 Flexible vs Expanded
Flexible lets a child take only the space it needs, while Expanded forces the child to fill all available space. Use Flexible for content that varies in size and Expanded for content that should stretch.
Responsive Design Patterns
Flutter doesn't have CSS media queries, but you can achieve responsive layouts using LayoutBuilder and MediaQuery:
responsive_layout.dart
class ResponsiveLayout extends StatelessWidget {
final Widget mobile;
final Widget tablet;
final Widget desktop;
const ResponsiveLayout({
required this.mobile,
required this.tablet,
required this.desktop,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth 600) return mobile;
if (constraints.maxWidth 1200) return tablet;
return desktop;
},
);
}
}
Row & Column
Row arranges children horizontally; Column arranges vertically. Both use the same alignment properties:
row_column.dart
// Row — horizontal layout
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.menu),
Text('dftacademy'),
Icon(Icons.account_circle),
],
)
// Column — vertical layout
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Title', style: Theme.of(context).textTheme.headlineMedium),
SizedBox(height: 8),
Text('Subtitle text here'),
SizedBox(height: 16),
Divider(),
Text('Body content...'),
],
)
Flex & Expanded
Expanded makes a child fill available space. Flexible allows a child to be smaller:
flex_layout.dart
Row(children: [
Expanded(
flex: 2,
child: Container(color: Colors.blue, height: 100),
),
SizedBox(width: 16),
Expanded(
flex: 1,
child: Container(color: Colors.red, height: 100),
),
])
Stack
Stack overlays widgets on top of each other. Use Positioned for precise placement:
stack_layout.dart
Stack(
clipBehavior: Clip.none,
children: [
Container(
width: 200, height: 200,
decoration: BoxDecoration(
color: Colors.indigo,
borderRadius: BorderRadius.circular(16),
),
),
Positioned(
top: -10, right: -10,
child: CircleAvatar(backgroundColor: Colors.red,
child: Text('3')),
),
Positioned(
bottom: 16, left: 16,
child: Text('Card Title', style: TextStyle(color: Colors.white)),
),
],
)
ListView
Scrollable list of widgets. Use ListView.builder for large datasets (lazy rendering):
listview_example.dart
// Static ListView
ListView(children: [
ListTile(leading: Icon(Icons.inbox), title: Text('Inbox')),
ListTile(leading: Icon(Icons.send), title: Text('Sent')),
ListTile(leading: Icon(Icons.drafts), title: Text('Drafts')),
])
// Lazy ListView.builder (recommended for large lists)
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].name),
subtitle: Text(items[index].description),
trailing: Icon(Icons.chevron_right),
);
},
)
GridView
2D scrollable grid. Perfect for dashboards and media galleries:
gridview_example.dart
GridView.count(
crossAxisCount: 3,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
padding: const EdgeInsets.all(16),
children: dashboardItems.map((item) {
return Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(item.icon, size: 32),
SizedBox(height: 8),
Text(item.label),
],
),
);
}).toList(),
)
Responsive Design
Use LayoutBuilder and MediaQuery for responsive layouts:
responsive.dart
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
// Desktop: side-by-side
return Row(children: [
Expanded(child: Sidebar()),
Expanded(flex: 3, child: Content()),
]);
} else {
// Mobile: stacked
return Column(children: [
Content(),
]);
}
},
)
Real-World Example: Media Dashboard Grid
dashboard.dart
class MediaDashboard extends StatelessWidget {
const MediaDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Media Dashboard')),
body: LayoutBuilder(
builder: (context, constraints) {
var crossCount = constraints.maxWidth > 900 ? 4
: constraints.maxWidth > 600 ? 3 : 2;
return GridView.count(
crossAxisCount: crossCount,
padding: const EdgeInsets.all(16),
mainAxisSpacing: 16,
crossAxisSpacing: 16,
childAspectRatio: 0.85,
children: List.generate(12, (index) {
return Card(
clipBehavior: Clip.antiAlias,
child: Column(crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: Container(color: Colors.indigo[100 + index * 50])),
Padding(padding: const EdgeInsets.all(12),
child: Column(crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Video ${index + 1}', style: const TextStyle(fontWeight: FontWeight.bold)),
Text('${(index + 1) * 3} min', style: TextStyle(color: Colors.grey[600])),
],
),
),
],
),
);
}),
);
},
),
);
}
}
Summary
Row/Columnfor directional layouts,Stackfor overlaysExpandedandFlexiblecontrol space distributionListView.builderfor lazy lists,GridView.countfor gridsLayoutBuilderenables responsive breakpoints
🚀 Next Step Continue to State Management.