📐LAYOUT
📱RESPONSIVE
🎯FLEX
🗂️STACK
📊GRID
UI
BAB 6
Flutter Layout & Responsive UI
Membangun UI Profesional
📐 Flex Layout 🗂️ Stack 📜 ListView 📊 GridView 📱 Responsive
Peta Konsep

🗺️Apa yang Akan Kita Pelajari

10 topik besar yang mengubahmu dari "bisa pakai widget" menjadi "bisa menata UI seperti aplikasi Play Store".

↔️Flex LayoutRow, Column, alignment, Expanded
🗂️StackTumpuk & posisi absolut
📜ListViewDaftar yang bisa di-scroll
📊GridViewTata grid seperti galeri
📃ScrollViewKonten panjang anti-overflow
📏MediaQueryBaca ukuran layar device
📐LayoutBuilderLayout adaptif by constraint
💧ResponsiveStrategi breakpoint
🧩UI PatternScaffold, nav, card
🚀Mini ProjectDashboard mobile
🎯 Tujuan akhir: Di slide terakhir kamu akan membangun sebuah Dashboard Mobile UI lengkap menggunakan SEMUA konsep di atas.
Pendahuluan

🏠Apa Itu Layout?

Di Bab 5 kamu sudah kenal widget (Text, Icon, Container). Tapi widget sendirian itu seperti perabot yang berserakan. Layout adalah seni menata widget-widget itu agar rapi, enak dilihat, dan berfungsi.

🏡 Analogi: Denah Rumah

Bayangkan kamu membangun rumah. Setiap ruangan = widget. Cara kamu menata ruangan (mana di kiri, mana di atas, mana yang lebih luas) = layout. Tanpa denah, rumah jadi berantakan!

🏠 Atap
Kamar
Dapur
🛋️ Ruang Tamu
🚪

💡 Penjelasan

Di Flutter, layout dilakukan dengan widget layout khusus yang membungkus widget lain:

  • Row & Column → menyusun berbaris
  • Stack → menumpuk bertumpuk
  • ListView & GridView → daftar & grid
  • Padding, Center, Expanded → pengatur ruang
Ingat: Di Flutter, SEMUANYA adalah widget — bahkan layout pun widget. Kita menata UI dengan cara membungkus widget di dalam widget lain (nested).
Flex Layout 1/4

↔️Row — Menyusun Mendatar

Row menyusun widget anak-anaknya dari kiri ke kanan dalam satu baris horizontal.

🎬 Analogi: Bangku Bioskop

Penonton duduk berjajar dari kiri ke kanan dalam satu baris. Tidak ada yang menumpuk — semua sejajar mendatar. Itulah Row.

🎬 LAYAR
🧑
👩
🧒
Demo Visual
A
B
C
row.dart
Row(
  mainAxisAlignment: MainAxisAlignment.center, // perataan horizontal
  children: [
    Icon(Icons.star, color: Colors.amber),
    SizedBox(width: 12),                        // jarak antar anak
    Text('Bintang 5'),
    SizedBox(width: 12),
    Icon(Icons.favorite, color: Colors.red),
  ],
)
Contoh nyata: harga + rating produk dalam satu baris
row_harga.dart
Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween, // dorong ke 2 ujung
  children: [
    Text('Rp 25.000',
      style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
    Row(                                  // Row bisa bersarang di dalam Row
      children: const [
        Icon(Icons.star, size: 16, color: Colors.amber),
        SizedBox(width: 4),
        Text('4.8'),
      ],
    ),
  ],
)
Penting: Sumbu utama (main axis) Row adalah horizontal. Gunakan SizedBox(width: ...) untuk memberi jarak antar anak. Row juga boleh bersarang di dalam Row lain.
Flex Layout 2/4

↕️Column — Menyusun Menurun

Column menyusun widget anak-anaknya dari atas ke bawah dalam satu kolom vertikal.

📚 Analogi: Tumpukan Buku

Buku ditumpuk satu per satu dari bawah ke atas di rak. Setiap buku punya posisi vertikalnya sendiri, tidak berjajar ke samping. Itulah Column.

📕 Dart
📗 Flutter
📘 Layout
📙 Project
Demo Visual
Judul
Subjudul
Tombol
column.dart
Column(
  mainAxisAlignment: MainAxisAlignment.start, // perataan vertikal (atas)
  crossAxisAlignment: CrossAxisAlignment.start, // perataan horizontal (kiri)
  children: [
    Text('Profil Saya', style: TextStyle(fontSize: 22)),
    SizedBox(height: 8),
    Text('Pelajar Flutter'),
    SizedBox(height: 16),
    ElevatedButton(onPressed: () {}, child: Text('Ikuti')),
  ],
)
Contoh nyata: kartu menu — judul di atas, harga & tombol di bawah
column_menu.dart
Column(
  crossAxisAlignment: CrossAxisAlignment.start, // semua rata kiri
  children: [
    Text('🍔 Burger Spesial',
      style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
    SizedBox(height: 4),
    Text('Daging sapi + keju + saus rahasia',
      style: TextStyle(color: Colors.grey)),
    SizedBox(height: 12),
    Row(                                  // Row di dalam Column
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text('Rp 30.000', style: TextStyle(color: Colors.amber)),
        ElevatedButton(onPressed: () {}, child: Text('+ Keranjang')),
      ],
    ),
  ],
)
Beda Row vs Column: Row = main axis horizontal. Column = main axis vertikal. Maka pada Column, SizedBox(height: ...) yang memberi jarak. Pola Column berisi Row ini paling sering dipakai membuat kartu.
Flex Layout 3/4

🎯MainAxis & CrossAxis Alignment

Dua "tombol pengatur perataan" pada Row/Column. Coba sendiri di playground interaktif di bawah!

🛍️ Analogi: Menata Etalase Toko

MainAxisAlignment = mengatur barang sepanjang sumbu utama (mis. dirapatkan, diregangkan, diratakan tengah). CrossAxisAlignment = mengatur sumbu menyilang (mis. semua barang rata atas / rata bawah).

🎮 Playground — Ubah Alignment Row
mainAxisAlignment
crossAxisAlignment
1
2
3
alignment.dart
Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween, // sumbu utama
  crossAxisAlignment: CrossAxisAlignment.center,      // sumbu silang
  children: [ Box(1), Box(2), Box(3) ],
)
Flex Layout 4/4

📏Expanded & Flexible

Cara membagi ruang sisa di dalam Row/Column secara proporsional.

🍽️ Analogi: Membagi Meja Makan

Tiga orang berbagi meja. Dengan flex: 1, 2, 1 → orang tengah dapat porsi 2× lebih lebar. Expanded memaksa mengisi semua ruang sisa.

🍚
🍗
🥗
flex: 1flex: 2 ↔flex: 1
🎮 Playground — Atur flex
flex kotak tengah
kiri
Expanded
kanan
expanded.dart
Row(
  children: [
    Expanded(flex: 1, child: Container(color: Colors.purple)),
    Expanded(flex: 2, child: Container(color: Colors.amber)), // 2x lebih lebar
    Flexible(                                  // boleh lebih kecil dari porsi
      fit: FlexFit.loose,
      child: Container(color: Colors.orange),
    ),
  ],
)
Contoh nyata: search bar melar + tombol filter tetap kecil
searchbar.dart
Row(
  children: [
    Expanded(                          // kolom pencarian isi semua ruang sisa
      child: TextField(
        decoration: InputDecoration(hintText: 'Cari produk...'),
      ),
    ),
    SizedBox(width: 8),
    Icon(Icons.filter_list),           // tombol filter ukurannya tetap
  ],
)
Bedanya: Expanded = WAJIB mengisi penuh ruang sisa (fit: tight). Flexible = BOLEH mengisi, tapi tidak memaksa (fit: loose). Pola di atas membuat input panjang otomatis menyesuaikan layar.
Layout Bertumpuk

🗂️Stack & Positioned

Berbeda dari Row/Column yang berjajar, Stack menumpuk widget di atas widget lain — cocok untuk overlay, badge, dan teks di atas gambar.

🃏 Analogi: Tumpukan Kartu / Layer Photoshop

Seperti kartu yang ditumpuk: kartu paling bawah ditulis pertama, kartu paling atas menutupinya. Positioned menentukan posisi tepat (top/left/right/bottom) tiap layer.

1
2
3
Demo: Avatar + Badge Online
🦊
PRO
stack.dart
Stack(
  children: [
    CircleAvatar(radius: 45, child: Text('🦊')), // layer paling bawah
    Positioned(                                    // badge online di kanan-bawah
      bottom: 4, right: 4,
      child: Container(
        width: 18, height: 18,
        decoration: BoxDecoration(
          color: Colors.green,
          shape: BoxShape.circle,
          border: Border.all(color: Colors.white, width: 3),
        ),
      ),
    ),
  ],
)
Contoh nyata: teks judul di atas gambar (banner)
banner_stack.dart
Stack(
  children: [
    Container(                          // layer bawah: gambar/gradien
      height: 160,
      decoration: BoxDecoration(
        gradient: LinearGradient(colors: [Colors.purple, Colors.amber]),
        borderRadius: BorderRadius.circular(16),
      ),
    ),
    Positioned(                         // layer atas: teks di pojok kiri-bawah
      left: 16, bottom: 16,
      child: Text('Promo Spesial 🔥',
        style: TextStyle(color: Colors.white, fontSize: 22,
          fontWeight: FontWeight.bold)),
    ),
  ],
)
Scrollable List

📜ListView — Daftar yang Bisa Di-scroll

Ketika datamu banyak (kontak, chat, produk), ListView menampilkannya dalam daftar yang bisa di-scroll dengan efisien.

🛒 Analogi: Daftar Belanja

Daftar belanja yang panjang tidak muat di satu layar — kamu menggulungnya ke bawah untuk melihat item berikutnya. ListView.builder membangun item hanya saat akan terlihat (hemat memori).

Susu
Roti
Telur
Kopi
Gula
Susu
Roti
Telur
Kopi
Gula

3 Cara Membuat ListView

  • ListView(children: [...]) — untuk item sedikit & tetap
  • ListView.builder — untuk item banyak/dinamis (paling sering!)
  • ListView.separated — otomatis beri pemisah antar item

Gunakan .builder kalau jumlah item banyak atau berasal dari API.

listview.dart
final kontak = ['Andi', 'Budi', 'Citra', 'Dewi'];

ListView.builder(
  itemCount: kontak.length,
  itemBuilder: (context, index) {
    return ListTile(
      leading: CircleAvatar(child: Text(kontak[index][0])),
      title: Text(kontak[index]),
      subtitle: Text('Online'),
      trailing: Icon(Icons.chat),
    );
  },
)
Variasi: ListView.separated — otomatis kasih garis pemisah
listview_separated.dart
ListView.separated(
  itemCount: kontak.length,
  itemBuilder: (context, index) => ListTile(title: Text(kontak[index])),
  separatorBuilder: (context, index) => Divider(), // pemisah antar item
)
Grid Layout

📊GridView — Tata Letak Grid

Untuk galeri, dashboard, atau katalog produk — GridView menyusun item dalam beberapa kolom sekaligus.

📸 Analogi: Galeri Instagram / Rak Telur

Foto-foto disusun rapi dalam kotak-kotak berbaris dan berkolom — seperti telur di raknya. crossAxisCount menentukan berapa kolom per baris.

🌅
🏞️
🌄
🌃
🌌
🏔️
🌊
🏖️
🌋

Properti Kunci

  • crossAxisCount — jumlah kolom (mis. 3)
  • childAspectRatio — rasio lebar:tinggi tiap sel
  • mainAxisSpacing — jarak antar baris
  • crossAxisSpacing — jarak antar kolom
gridview.dart
GridView.builder(
  itemCount: 9,
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,        // 3 kolom per baris
    childAspectRatio: 1,      // sel berbentuk persegi
    mainAxisSpacing: 8,
    crossAxisSpacing: 8,
  ),
  itemBuilder: (context, index) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.purple.shade300,
        borderRadius: BorderRadius.circular(12),
      ),
      child: Center(child: Text('Foto $index')),
    );
  },
)
Anti-Overflow

📃SingleChildScrollView

Pernah lihat error kuning-hitam "RenderFlex overflowed"? Ini penyelamatnya saat konten lebih panjang dari layar.

📜 Analogi: Gulungan Kertas

Sebuah Column yang isinya panjang (mis. form pendaftaran) akan "meluber" keluar layar. Bungkus dengan SingleChildScrollView → seperti gulungan kertas yang bisa kamu gulung naik-turun.

Nama
Email
Alamat
No. HP
Sandi
Nama
Email
Alamat
No. HP
Sandi
📃 SingleChildScrollView

Untuk SATU child panjang (mis. Column berisi form). Membangun SEMUA isi sekaligus. Pilih jika item sedikit & tidak repetitif.

📜 ListView.builder

Untuk BANYAK item sejenis/berulang. Hanya membangun item yang terlihat → jauh lebih hemat memori untuk data besar.

scrollview.dart
SingleChildScrollView(
  scrollDirection: Axis.vertical,           // arah scroll
  physics: BouncingScrollPhysics(),         // efek pantul ala iOS
  child: Column(
    children: [
      TextField(decoration: InputDecoration(labelText: 'Nama')),
      TextField(decoration: InputDecoration(labelText: 'Email')),
      TextField(decoration: InputDecoration(labelText: 'Alamat')),
      // ... banyak field lain, aman dari overflow
    ],
  ),
)
Membaca Layar

📏MediaQuery — Mengenali Device

Aplikasi profesional tahu ukuran layarnya. MediaQuery memberi tahu lebar, tinggi, orientasi, dan kerapatan piksel device.

👕 Analogi: Label Ukuran Baju

Baju punya label S/M/L/XL agar pas dengan tubuh pemakainya. MediaQuery membaca "ukuran tubuh" device (HP kecil, tablet, desktop) supaya UI bisa menyesuaikan diri.

📱
Yang Bisa Dibaca MediaQuery
  • size.width / size.height — lebar & tinggi layar
  • orientation — portrait / landscape
  • devicePixelRatio — kerapatan piksel
  • padding — area aman (notch, status bar)
  • textScaleFactor — skala font sistem
mediaquery.dart
@override
Widget build(BuildContext context) {
  final lebar = MediaQuery.of(context).size.width; // baca lebar layar
  final isTablet = lebar > 600;

  return Text(
    isTablet ? 'Mode Tablet 🖥️' : 'Mode HP 📱',
    style: TextStyle(fontSize: isTablet ? 28 : 16),
  );
}
Tips: MediaQuery cocok untuk keputusan berdasarkan ukuran SELURUH LAYAR. Untuk ukuran sebuah kotak/area tertentu, gunakan LayoutBuilder (slide berikutnya).
Layout Adaptif

📐LayoutBuilder — Mengukur Ruang

LayoutBuilder memberi tahu ukuran ruang yang tersedia (constraints) untuk widget DI dalamnya — bukan seluruh layar.

📐 Analogi: Arsitek Mengukur Ruangan

Sebelum mendesain, arsitek mengukur ruangan dulu. "Kalau ruangan lebar, taruh 3 lemari; kalau sempit, cukup 1." LayoutBuilder mengukur maxWidth lalu memutuskan layout.

📏
maxWidth = ?
📏 MediaQuery

Ukuran seluruh layar device. Sama di mana pun widget berada.

📐 LayoutBuilder

Ukuran area lokal tempat widget dipasang. Bisa beda untuk tiap bagian UI.

layoutbuilder.dart
LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth > 600) {
      // ruang lebar -> tampilkan grid 3 kolom
      return GridView.count(crossAxisCount: 3, children: items);
    } else {
      // ruang sempit -> tampilkan daftar 1 kolom
      return ListView(children: items);
    }
  },
)
Responsive Design 1/2

💧Konsep Responsive Design

Satu aplikasi, banyak ukuran layar. UI yang baik menyesuaikan diri dengan elegan — tidak pecah, tidak kosong melompong.

💧 Analogi: Air Menyesuaikan Wadah

Air selalu pas dengan bentuk wadahnya — gelas, botol, atau mangkuk. UI responsif seperti air: mengisi ruang yang ada dengan pas, di HP maupun tablet.

🔄 Responsive

UI melar/mengecil mulus mengikuti lebar layar (mis. lebar kartu = 30% layar). Tata letak sama, ukuran menyesuaikan.

🎯 Adaptive

UI berganti tata letak di titik tertentu (mis. < 600px pakai bottom nav, > 600px pakai side rail). Beda layar, beda struktur.

Contoh: lebar kartu = 30% layar (responsive mulus)
responsive.dart
final lebarLayar = MediaQuery.of(context).size.width;

Container(
  width: lebarLayar * 0.3,   // selalu 30% dari lebar layar -> ikut mengecil/melar
  height: 120,
  decoration: BoxDecoration(
    color: Colors.purple,
    borderRadius: BorderRadius.circular(16),
  ),
)
Intinya: Responsive = skala mulus. Adaptive = ganti layout di breakpoint. Aplikasi profesional sering memakai keduanya sekaligus.
Responsive Design 2/2

🚦Strategi Breakpoint

Breakpoint = batas lebar tempat layout berubah. Tiga zona umum: mobile, tablet, desktop.

🏃 Analogi: Zona di Lintasan Lari

Lintasan lari dibagi zona-zona dengan garis batas. Begitu pelari melewati garis, aturan zona berubah. Breakpoint adalah "garis" tempat layout aplikasi berganti aturan.

📱 Mobile < 600📲 Tablet 600–900🖥️ Desktop > 900
breakpoint.dart
Widget pilihLayout(double lebar) {
  if (lebar < 600) {
    return LayoutMobile();   // 1 kolom + bottom nav
  } else if (lebar < 900) {
    return LayoutTablet();   // 2 kolom
  } else {
    return LayoutDesktop();  // 3 kolom + side navigation
  }
}

// Dipakai bersama LayoutBuilder atau MediaQuery
LayoutBuilder(
  builder: (ctx, c) => pilihLayout(c.maxWidth),
)
Tips praktis: Mulailah desain dari layar tersempit (mobile-first), lalu tambahkan kolom/elemen saat ruang bertambah. Lebih mudah daripada sebaliknya.
UI Pattern 1/3

🏗️Scaffold — Kerangka Halaman

Hampir setiap halaman Flutter dibungkus Scaffold. Ia menyediakan "slot" standar: app bar, body, tombol melayang, dan navigasi bawah.

🏗️ Analogi: Kerangka Rumah

Sebelum mengisi perabot, rumah perlu kerangka: atap, lantai, dinding. Scaffold adalah kerangka layar — kamu tinggal mengisi tiap slotnya.

AppBar
body
+
Slot Utama Scaffold
  • appBar — bilah atas (judul + aksi)
  • body — konten utama halaman
  • floatingActionButton — tombol bulat melayang
  • bottomNavigationBar — navigasi bawah
  • drawer — menu samping geser
scaffold.dart
Scaffold(
  appBar: AppBar(title: Text('Beranda')),
  body: Center(child: Text('Konten utama di sini')),
  floatingActionButton: FloatingActionButton(
    onPressed: () {},
    child: Icon(Icons.add),
  ),
  bottomNavigationBar: BottomNavigationBar(items: [
    BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
    BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profil'),
  ]),
)
UI Pattern 2/3

🧭Navigation — Berpindah Halaman

BottomNavigationBar adalah pola navigasi paling umum di aplikasi mobile. Coba klik tab di bawah!

🏬 Analogi: Papan Petunjuk Mall

Di mall, papan petunjuk membantumu pindah area: Food Court, Bioskop, Toko. Bottom nav melakukan hal sama — pindah antar halaman utama aplikasi.

🏠
🏠Home
🔍Cari
👤Profil
Cara Kerja
  • currentIndex menyimpan tab yang aktif
  • onTap mengubah index → setState
  • body menampilkan halaman sesuai index
  • Sering dipadukan dengan PageView / IndexedStack
navigation.dart
int _index = 0;
final _halaman = [HalamanHome(), HalamanCari(), HalamanProfil()];

Scaffold(
  body: _halaman[_index],                  // tampilkan halaman aktif
  bottomNavigationBar: BottomNavigationBar(
    currentIndex: _index,
    onTap: (i) => setState(() => _index = i), // ganti tab
    items: [
      BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
      BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Cari'),
      BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profil'),
    ],
  ),
)
UI Pattern 3/3

🪪Card & ListTile

Dua widget Material Design yang membuat daftar terlihat profesional secara instan: kartu dengan bayangan, dan baris siap pakai.

💳 Analogi: Kartu Nama

Kartu nama merangkum info penting dalam wadah rapi bertepi & berbayang. Card memberi elevasi (shadow); ListTile menyediakan struktur baris: ikon kiri, judul, subjudul, aksi kanan.

🎧
Lo-Fi Beats
Playlist · 42 lagu
Anatomi ListTile
  • leading — widget di kiri (avatar/ikon)
  • title — teks utama
  • subtitle — teks pendukung
  • trailing — widget di kanan (panah/ikon)
  • onTap — aksi saat baris diketuk
card_listtile.dart
Card(
  elevation: 4,                              // tinggi bayangan
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(14),
  ),
  child: ListTile(
    leading: CircleAvatar(child: Icon(Icons.music_note)),
    title: Text('Lo-Fi Beats'),
    subtitle: Text('Playlist · 42 lagu'),
    trailing: Icon(Icons.play_arrow),
    onTap: () {},
  ),
)
Mini Project 1/2

📋Dashboard Mobile — Desain

Sebelum coding, mari rancang dulu. Inilah widget tree dashboard yang akan kita bangun — gabungan semua konsep Bab 6.

1
Scaffold + AppBarKerangka halaman & judul "Dashboard" + ikon notifikasi
2
Hero Section (Stack)Banner gradien dengan teks sambutan di-overlay menggunakan Stack
3
Stats Row (Row + Expanded)3 kartu statistik berbagi lebar sama rata
4
Featured (ListView horizontal)Daftar kartu unggulan yang bisa di-scroll ke samping
5
Kategori (GridView)Grid 2 kolom kategori menu
6
BottomNavigationBarNavigasi 3 tab di bagian bawah
💡 Pondasi: Seluruh body dibungkus SingleChildScrollView agar bisa di-scroll, dan bagian grid memakai LayoutBuilder agar responsif (2 kolom di HP, 3 di tablet).
Mini Project 2/2

🚀Dashboard Mobile — Implementasi

Kode lengkap di kiri, hasil nyata di "HP" sebelah kanan. Inilah hasil menggabungkan semua konsep Bab 6.

dashboard.dart
Scaffold(
  appBar: AppBar(
    title: Text('Dashboard'),
    actions: [IconButton(icon: Icon(Icons.notifications), onPressed: () {})],
  ),
  body: SingleChildScrollView(            // anti-overflow
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 1) HERO dengan Stack overlay
        Stack(children: [
          Container(height: 120, decoration: BoxDecoration(
            gradient: LinearGradient(colors: [Colors.purple, Colors.amber]))),
          Positioned(left: 16, bottom: 16,
            child: Text('Halo, Sahil 👋',
              style: TextStyle(color: Colors.white, fontSize: 20))),
        ]),
        // 2) STATS dengan Row + Expanded
        Padding(padding: EdgeInsets.all(12), child: Row(children: [
          Expanded(child: _kartuStat('Tugas', '12')),
          SizedBox(width: 8),
          Expanded(child: _kartuStat('Selesai', '8')),
          SizedBox(width: 8),
          Expanded(child: _kartuStat('Nilai', '92')),
        ])),
        // 3) FEATURED dengan ListView horizontal
        SizedBox(height: 90, child: ListView.builder(
          scrollDirection: Axis.horizontal,
          itemCount: 5,
          itemBuilder: (c, i) => _kartuFeatured(i))),
        // 4) KATEGORI responsif dengan LayoutBuilder + GridView
        LayoutBuilder(builder: (c, cons) {
          final kolom = cons.maxWidth > 600 ? 3 : 2;
          return GridView.count(
            crossAxisCount: kolom, shrinkWrap: true,
            physics: NeverScrollableScrollPhysics(),
            children: kategori.map(_kartuKategori).toList());
        }),
      ],
    ),
  ),
  bottomNavigationBar: BottomNavigationBar(items: const [
    BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
    BottomNavigationBarItem(icon: Icon(Icons.grid_view), label: 'Menu'),
    BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profil'),
  ]),
)
Dashboard🔔
Halo, Sahil 👋
12Tugas
8Selesai
92Nilai
Unggulan
📘
🎯
🏆
Kategori
📚
🧪
🎨
🏃
🏠🔲👤
👈 Mock HP menampilkan hasil kode. Scroll di dalam frame untuk melihat seluruh dashboard.
Penutup

🎉Ringkasan Bab 6

Luar biasa! Kamu telah menempuh lompatan besar dari "memakai widget" menjadi "menata UI profesional". Inilah yang sudah kamu kuasai:

Flex LayoutRow, Column, alignment, Expanded
StackOverlay & Positioned
ListViewDaftar scroll efisien
GridViewTata letak grid
ScrollViewAnti-overflow
MediaQueryBaca ukuran layar
LayoutBuilderAdaptif by constraint
ResponsiveBreakpoint strategy
UI PatternScaffold, nav, card
DashboardMini project selesai
🔮 Bab 7 — Sneak Peek: Selanjutnya kita belajar membuat UI yang HIDUP: State Management & interaksi pengguna. UI-mu akan bisa berubah saat ditekan, diketik, dan digeser!
✔ 10 Topik Layout✔ Responsive UI✔ Dashboard App✔ Siap Bab 7
Latihan 1Mudah

🎯Tantangan Layout Dasar

Bangun sebuah UI kartu profil pengguna dengan menggabungkan Row, Column, dan Stack.

📌 Spesifikasi:
  • Avatar lingkaran di bagian atas (CircleAvatar)
  • Nama & bio tersusun ke bawah di bawah avatar (Column)
  • Badge "Online" hijau menempel di pojok avatar (Stack + Positioned)
  • Baris statistik: Posts, Followers, Following berbagi lebar sama (Row + Expanded)
latihan1_starter.dart
Column(
  children: [
    // TODO 1: Bungkus CircleAvatar dengan Stack + Positioned
    //         untuk menambahkan badge "Online" di pojok kanan-bawah
    Stack(
      children: [
        CircleAvatar(radius: 50, backgroundColor: Colors.purple),
        // TODO: tambahkan Positioned berisi titik hijau di sini
      ],
    ),
    SizedBox(height: 12),
    // TODO 2: Tampilkan nama (besar tebal) dan bio (abu-abu) dengan Column
    Text('___'),

    SizedBox(height: 20),
    // TODO 3: Buat Row 3 statistik, masing-masing dibungkus Expanded
    Row(
      children: [
        // Expanded( child: _stat('Posts', '128') ),
        // ...
      ],
    ),
  ],
)
Stack + Positioned untuk badge30%
Column nama & bio rapi25%
Row + Expanded statistik rata30%
Kerapian jarak (SizedBox/Padding)15%
Latihan 2Sedang

🖼️Grid Gallery Challenge

Bangun galeri foto responsif: 3 kolom di layar lebar, 2 kolom di layar sempit, dengan GridView + LayoutBuilder.

📌 Spesifikasi:
  • Layar lebar (> 600px) → tampilkan 3 kolom
  • Layar sempit (≤ 600px) → tampilkan 2 kolom (gunakan LayoutBuilder)
  • Setiap item berisi: gambar/ikon + judul + rating (Column di dalam sel)
  • Bonus: efek loading shimmer saat data belum siap
latihan2_starter.dart
LayoutBuilder(
  builder: (context, constraints) {
    // TODO 1: Tentukan jumlah kolom berdasarkan constraints.maxWidth
    final int kolom = /* ___ ? 3 : 2 */ 2;

    return GridView.builder(
      itemCount: foto.length,
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: kolom,
        childAspectRatio: 0.8,
        mainAxisSpacing: 10,
        crossAxisSpacing: 10,
      ),
      itemBuilder: (context, index) {
        // TODO 2: Kembalikan Card berisi Column [gambar, judul, rating]
        return Card(child: Column(children: [ /* ... */ ]));
      },
    );
  },
)
LayoutBuilder ganti kolom dengan benar35%
GridView tampil rapi tanpa overflow30%
Isi sel (gambar + judul + rating)25%
Bonus: shimmer loading+10%
Tugas Akhir Bab 6Menantang

🏆Mini Dashboard App — Tugas Mandiri

Gabungkan SEMUA konsep Bab 6 menjadi satu aplikasi Dashboard utuh yang responsif.

📌 Spesifikasi Lengkap:
  • AppBar dengan judul dan satu tombol aksi (mis. notifikasi)
  • Hero section banner dengan teks di-overlay (Stack)
  • Stats row — 3 kartu dalam Row + Expanded
  • ListView horizontal untuk item unggulan (featured)
  • GridView vertikal untuk daftar kategori
  • BottomNavigationBar dengan 3 tab
  • Responsif — rapi di portrait maupun landscape (MediaQuery/LayoutBuilder)
1. Scaffold & AppBar lengkap10
2. Hero Stack overlay benar15
3. Stats Row + Expanded rata15
4. ListView horizontal jalan15
5. GridView kategori rapi15
6. BottomNav 3 tab berfungsi10
7. Responsif portrait/landscape10
8. Anti-overflow (ScrollView)5
9. Kerapian & konsistensi tema3
10. Kreativitas & kebersihan kode2
📅 Pengumpulan: Kumpulkan file .dart + screenshot tampilan (portrait & landscape) ke folder tugas kelas. Tuliskan komentar singkat di tiap section kode. Selamat berkarya — tunjukkan UI terbaikmu! 🚀