Flutter: Widgets & Layout
Everything in Flutter is a widget. The widget tree is rebuilt efficiently by Flutter's rendering engine. StatelessWidget for static UI; StatefulWidget for UI that changes over time.
StatelessWidget & StatefulWidget
// StatelessWidget — no mutable state
class GreetingCard extends StatelessWidget {
final String name;
final VoidCallback onTap;
const GreetingCard({
super.key,
required this.name,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text('Hello, $name!',
style: Theme.of(context).textTheme.headlineSmall),
),
),
);
}
}
// StatefulWidget — mutable state with setState
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
void _increment() {
setState(() { _count++; }); // triggers rebuild
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('$_count', style: const TextStyle(fontSize: 48)),
ElevatedButton(onPressed: _increment, child: const Text('+')),
],
);
}
@override
void initState() {
super.initState();
// called once when widget is inserted into tree
}
@override
void dispose() {
// cleanup: cancel timers, close streams
super.dispose();
}
}Layout Widgets
// Column and Row — flex containers
Column(
mainAxisAlignment: MainAxisAlignment.center, // vertical axis
crossAxisAlignment: CrossAxisAlignment.start, // horizontal axis
children: [
Text('Title'),
const SizedBox(height: 8), // spacer
Text('Subtitle'),
],
)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Left'),
Expanded(child: Text('Takes remaining space')),
Text('Right'),
],
)
// Stack — overlapping widgets
Stack(
alignment: Alignment.bottomCenter,
children: [
Image.network(imageUrl),
Container(
color: Colors.black54,
padding: const EdgeInsets.all(8),
child: const Text('Caption', style: TextStyle(color: Colors.white)),
),
],
)
// ListView — scrollable list
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ListTile(
leading: const Icon(Icons.person),
title: Text(items[index].name),
subtitle: Text(items[index].email),
trailing: const Icon(Icons.chevron_right),
onTap: () => Navigator.pushNamed(context, '/detail', arguments: items[index]),
),
)
// GridView
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1.5,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: products.length,
itemBuilder: (context, index) => ProductCard(product: products[index]),
)Common Widgets
// Scaffold — basic screen structure
Scaffold(
appBar: AppBar(
title: const Text('Home'),
actions: [IconButton(icon: const Icon(Icons.search), onPressed: () {})],
),
body: const Center(child: Text('Content')),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
currentIndex: _selectedIndex,
onTap: (index) => setState(() => _selectedIndex = index),
),
)
// Input
TextField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
hintText: 'alice@example.com',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(),
),
validator: (v) => v?.contains('@') == true ? null : 'Invalid email',
)
// Async image loading
Image.network(
imageUrl,
loadingBuilder: (context, child, progress) =>
progress == null ? child : const CircularProgressIndicator(),
errorBuilder: (context, error, stack) => const Icon(Icons.broken_image),
)Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free