Kalau widget adalah bata, layouting adalah cara kita menyusun bata itu jadi rumah yang rapi. Di Flutter, urusan tata letak itu hampir semuanya pakai widget juga — dan justru di situlah kekuatannya. Nggak ada XML layout terpisah, nggak ada constraint-based layout yang bikin pusing. Semua adalah widget, semua composable.
Hari ini kita bahas lima “alat” layouting paling fundamental: Padding & Margin, SizedBox, Expanded, dan Flexible. Kuasai kelimanya, dan kamu bisa bikin layout apapun.
1. Padding & Margin — Bedanya Apa?
Di Flutter, konsep “margin” dan “padding” disatukan dalam satu widget: Padding dan properti margin pada Container. Bedanya sederhana:
- Padding = ruang di dalam widget, antara border dan child
- Margin = ruang di luar widget, antara widget dan parent/saudaranya
Padding dengan Widget Padding
Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
color: Colors.blue,
child: const Text('Ada jarak 16 di semua sisi'),
),
)
EdgeInsets punya beberapa constructor yang berguna:
EdgeInsets.all(16) // semua sisi 16
EdgeInsets.symmetric( // horizontal 24, vertical 12
horizontal: 24,
vertical: 12,
)
EdgeInsets.only( // hanya kiri & atas
left: 32,
top: 8,
)
EdgeInsets.fromLTRB(8, 16, 8, 16) // left, top, right, bottom
Margin dengan Container
Container(
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: const EdgeInsets.all(12),
color: Colors.grey[200],
child: const Text('Margin luar 20 horizontal, padding dalam 12'),
)
Tips: Kalau cuma butuh padding tanpa dekorasi lain, pakai widget
Paddinglangsung.Containerlebih “berat” karena punya banyak properti. Untuk kasus simpel,Paddinglebih ringan secara rendering.
Visualisasi Box Model
┌────────── MARGIN ──────────┐
│ ┌────── BORDER ──────┐ │
│ │ ┌── PADDING ──┐ │ │
│ │ │ │ │ │
│ │ │ CONTENT │ │ │
│ │ │ │ │ │
│ │ └──────────────┘ │ │
│ └─────────────────────┘ │
└─────────────────────────────┘
2. SizedBox — Bikin Jarak & Ukuran Fix
SizedBox adalah widget paling sederhana untuk membuat ruang kosong dengan ukuran tertentu. Dua kegunaan utama:
Sebagai Spacer (Jarak Antar Widget)
Column(
children: [
ElevatedButton(onPressed: () {}, child: const Text('Tombol 1')),
const SizedBox(height: 16), // jarak vertikal 16 pixel
ElevatedButton(onPressed: () {}, child: const Text('Tombol 2')),
],
)
Sebagai Fixed-Size Container
SizedBox(
width: 200,
height: 100,
child: ElevatedButton(
onPressed: () {},
child: const Text('Lebar fix 200, tinggi 100'),
),
)
SizedBox vs Container
| Aspek | SizedBox | Container |
|---|---|---|
| Fungsi utama | Ukuran fix atau spacer | Multi-purpose box |
| Performance | Ringan | Lebih berat |
| Dekorasi | ❌ Tidak bisa | ✅ decoration, borderRadius |
| Transform | ❌ Tidak bisa | ✅ transform |
| Gunakan saat | Cuma perlu ukuran | Perlu styling (warna, border, shadow, dll) |
3. Expanded — Ambil Semua Sisa Ruang
Expanded adalah widget yang mengambil semua sisa ruang dari parent Row atau Column. Ini adalah senjata utama untuk layout responsif.
Row(
children: [
Container(width: 80, height: 50, color: Colors.red),
const SizedBox(width: 8),
Expanded(
child: Container(height: 50, color: Colors.blue),
), // ← mengambil sisa lebar setelah 80+8 pixel
],
)
Properti flex — Pembagian Proporsional
Kalau ada beberapa Expanded, properti flex menentukan rasio pembagiannya:
Row(
children: [
Expanded(
flex: 2,
child: Container(height: 50, color: Colors.green),
), // dapat 2/5 lebar
SizedBox(width: 8),
Expanded(
flex: 3,
child: Container(height: 50, color: Colors.orange),
), // dapat 3/5 lebar
],
)
Total flex = 2 + 3 = 5. Hijau dapat 2/5, orange dapat 3/5.
4. Flexible — Mirip Expanded, Tapi Bisa “Ngeliat” Dulu
Flexible adalah kakak yang lebih santai dari Expanded. Bedanya:
| Fitur | Expanded | Flexible |
|---|---|---|
| Default fit | FlexFit.tight |
FlexFit.loose |
| Memaksa isi | Widget child mengisi SEMUA ruang | Child bebas lebih kecil dari ruang |
| Cocok untuk | Widget yang memang harus full-width | Widget yang punya ukuran natural |
Row(
children: [
Flexible(
flex: 1,
child: Container(
height: 50,
color: Colors.purple,
child: const Text('Aku Flexible — tidak harus full'),
),
),
const SizedBox(width: 8),
Expanded(
flex: 1,
child: Container(
height: 50,
color: Colors.teal,
child: const Text('Aku Expanded — selalu full'),
),
),
],
)
Kapan Pakai Flexible vs Expanded?
- Expanded: Card, button, container yang harus memenuhi sisa ruang
- Flexible: Text panjang yang boleh wrap, chip/tag yang ukurannya natural
- Konversi mudah:
Expanded()sebenarnya sama denganFlexible(fit: FlexFit.tight)
5. Studi Kasus: Card Profile Responsif
Mari gabungkan semuanya dalam satu contoh nyata. Buat card profile yang:
- Ada avatar bulat di kiri
- Nama dan bio di kanan
- Responsif di berbagai ukuran layar
import 'package:flutter/material.dart';
class ProfileCard extends StatelessWidget {
const ProfileCard({super.key});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(16), // margin luar
child: Padding(
padding: const EdgeInsets.all(16), // padding dalam
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Avatar — ukuran fix 60x60
const SizedBox(
width: 60,
height: 60,
child: CircleAvatar(
radius: 30,
backgroundImage: NetworkImage('https://i.pravatar.cc/150'),
),
),
const SizedBox(width: 16), // spacer
// Info profile — ambil sisa lebar
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Ahmad Fathan',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'Senior Flutter Developer • Iya, bikin app dari nol sampai Play Store cuma modal ngopi.',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
],
),
),
);
}
}
Jalankan:
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: Scaffold(body: ProfileCard())));
Hasilnya: avatar 60×60 bulat di kiri, teks fleksibel di kanan. Kalau layar kecil, teks otomatis wrap — berkat Expanded yang membatasi lebar maksimalnya.
6. Cheat Sheet Cepat
Pengen jarak antar widget? → SizedBox(width/height: X)
Pengen padding di dalam? → Padding(edgeInsets: ...)
Pengen margin di luar? → Container(margin: ...)
Pengen isi sisa ruang penuh? → Expanded(flex: ...)
Pengen isi sisa ruang fleksibel? → Flexible(flex: ...)
Pengen ukuran fix? → SizedBox(width: X, height: Y)
Pengen ukuran fix + styling? → Container(width: X, height: Y, decoration: ...)
7. Common Mistakes (Jangan Dilakukan!)
❌ Expanded di luar Row/Column/Flex
// ERROR: Expanded harus child langsung dari Flex widget
Scaffold(
body: Expanded(child: ...), // ❌ Tidak bisa!
)
❌ Lupa crossAxisAlignment saat pakai Expanded
Row(
children: [
SizedBox(width: 60, height: 60, child: ...),
Expanded(
child: Column(...), // kalau Row, cross axis = vertical
),
],
)
// Tambahkan crossAxisAlignment: CrossAxisAlignment.start pada Row
// supaya avatar tidak stretch!
❌ Container banyak properti untuk hal simpel
// ❌ Berat
Container(
margin: const EdgeInsets.only(top: 12),
child: const Text('Halo'),
)
// ✅ Ringan
Padding(
padding: const EdgeInsets.only(top: 12),
child: const Text('Halo'),
)
8. Latihan Mandiri
Coba bikin layout berikut sendiri:
- Card produk: Gambar produk di atas (height fix 200), judul + harga + rating di bawah (pakai Padding + SizedBox + Expanded)
- Bar navigasi bawah buatan: Empat icon dalam Row, masing-masing pakai Expanded(flex: 1), biar lebar otomatis rata
- Chat bubble: Pesan kiri (flexible, align kiri) dan pesan kanan (flexible, align kanan) dalam satu Column
Penutup
Lima widget ini — Padding, SizedBox, Expanded, Flexible, dan Container(margin) — adalah 90% dari pekerjaan layouting kamu sehari-hari. Begitu paham kombinasi ketiganya, bikin UI kompleks pun rasanya kayak main lego: tinggal susun, atur jarak, atur fleksibilitas, beres.
Di artikel berikutnya kita akan naik level: ListView & GridView — bikin list konten yang bisa di-scroll, mulai dari list kontak, galeri foto, sampai feed berita.
Coba sendiri! Share hasil latihan kamu ke sosial media dan tag @ahsai001 🚀