Responsive Layouts with LayoutBuilder
Responsive Layouts with LayoutBuilder
What You’ll Learn
How to build truly responsive Flutter UIs that adapt to different screen sizes using LayoutBuilder - a powerful widget that provides parent widget constraints so you can make intelligent layout decisions at runtime.
Why LayoutBuilder Matters
While MediaQuery tells you about the entire screen, LayoutBuilder tells you about the specific space available to your widget. This makes it perfect for creating reusable components that adapt to their container, whether that’s a small card, a sidebar, or the full screen.
Example
Here’s how to build a card that switches between column and row layouts based on available width:
import 'package:flutter/material.dart';
class AdaptiveCard extends StatelessWidget {
final String title;
final String description;
final IconData icon;
const AdaptiveCard({
Key? key,
required this.title,
required this.description,
required this.icon,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: LayoutBuilder(
builder: (context, constraints) {
// Switch layout based on available width
final isWide = constraints.maxWidth > 300;
if (isWide) {
// Horizontal layout for wide containers
return Row(
children: [
Icon(icon, size: 48),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 4),
Text(description),
],
),
),
],
);
} else {
// Vertical layout for narrow containers
return Column(
children: [
Icon(icon, size: 48),
const SizedBox(height: 12),
Text(title,
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 4),
Text(description, textAlign: TextAlign.center),
],
);
}
},
),
),
);
}
}
// Usage example
class ResponsiveDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('LayoutBuilder Demo')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// Full width - will use Row layout
AdaptiveCard(
title: 'Wide Card',
description: 'This card has plenty of space',
icon: Icons.laptop,
),
const SizedBox(height: 16),
// Constrained width - will use Column layout
SizedBox(
width: 250,
child: AdaptiveCard(
title: 'Narrow Card',
description: 'This card is constrained',
icon: Icons.phone_android,
),
),
],
),
),
);
}
}
How It Works
The LayoutBuilder widget passes BoxConstraints to its builder function, which includes:
maxWidthandmaxHeight- maximum dimensions availableminWidthandminHeight- minimum dimensions required
You can use these constraints to make intelligent decisions about your UI structure, spacing, font sizes, and more.
Try It Yourself
Challenge: Extend the AdaptiveCard to support three layouts:
- Compact (< 250px): Icon on top, minimal text
- Normal (250-400px): The row layout from the example
- Expanded (> 400px): Add more details like a subtitle and action button
Bonus: Create a responsive grid that shows 1, 2, or 3 columns based on available width using LayoutBuilder inside a GridView.builder.
Tip of the Day
LayoutBuilder vs MediaQuery: Use MediaQuery.of(context).size when you need to know about the entire screen (like showing/hiding a drawer). Use LayoutBuilder when building reusable widgets that should adapt to their container size. This makes your components truly modular and testable at any size!
Performance note: LayoutBuilder rebuilds whenever its constraints change. For expensive widgets, consider using const constructors or memoization to avoid unnecessary rebuilds.