cardlisttilewidgetPemula3 mnt baca

Card & ListTile

Kartu Material dan baris daftar siap pakai dengan leading, title, trailing.

Analogi: Kartu Nama dan Daftar Belanja

Bayangkan kartu nama: selembar kertas agak tebal, sedikit terangkat dari meja karena bayangannya, berisi nama, jabatan, dan kontak dalam susunan yang selalu sama. Kamu langsung tahu cara membacanya tanpa diajari. Card di Flutter adalah "kartu nama" digital: permukaan yang sedikit terangkat (elevasi) dengan sudut membulat. Sedangkan ListTile seperti baris di daftar belanja: setiap baris punya pola tetap, ikon di kiri, nama barang di tengah, harga di kanan. Dua widget ini adalah fondasi hampir semua daftar di aplikasi: menu makanan, riwayat transaksi, daftar kontak.

Kenapa Card dan ListTile Itu Ada?

Material Design (bahasa desain Android) punya aturan: konten yang dikelompokkan tampil sebagai kartu yang terangkat dari latar, dan baris daftar punya struktur baku (leading, title, subtitle, trailing). Tanpa widget bawaan, setiap programmer akan merancang baris daftarnya sendiri dengan Row, Column, dan GestureDetector, hasilnya: tampilan tidak konsisten antar aplikasi dan efek sentuh (ripple) sering lupa dipasang. Card dan ListTile membakukan pola ini: kamu cukup mengisi slot yang tersedia, konsistensi dan efek Material didapat gratis.

Contoh 1: Card Sederhana

dart
Card(
  elevation: 4,
  margin: const EdgeInsets.all(8),
  child: const Padding(
    padding: EdgeInsets.all(16),
    child: Text('Isi kartu'),
  ),
)

elevation mengatur seberapa "terangkat" kartu terlihat (semakin besar, bayangan semakin lebar). Card punya margin bawaan, jadi kamu tidak perlu membungkusnya dengan Padding hanya untuk jarak antar kartu.

Contoh 2: Daftar Menu dengan ListView

Di aplikasi nyata, Card dan ListTile hampir selalu dipakai bersama di dalam daftar yang bisa di-scroll:

dart
class MenuList extends StatelessWidget {
  const MenuList({super.key});

  final items = const [
    ('Kopi Hitam', 'Rp10.000', Icons.coffee),
    ('Kopi Susu', 'Rp15.000', Icons.local_cafe),
    ('Matcha Latte', 'Rp18.000', Icons.emoji_food_beverage),
  ];

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      padding: const EdgeInsets.all(8),
      itemCount: items.length,
      itemBuilder: (context, index) {
        final (nama, harga, ikon) = items[index];
        return Card(
          child: ListTile(
            leading: Icon(ikon, color: Colors.brown),
            title: Text(nama),
            subtitle: const Text('Diseduh fresh setiap pesanan'),
            trailing: Text(harga,
                style: const TextStyle(fontWeight: FontWeight.bold)),
            onTap: () {
              // buka detail menu
            },
          ),
        );
      },
    );
  }
}

Setiap baris punya peran jelas: leading untuk ikon kategori, title untuk nama, subtitle untuk deskripsi kecil, trailing untuk harga atau panah navigasi. Pola ini dipakai aplikasi kasir, e-commerce, sampai aplikasi chat.

Catatan teknis: ListTile sudah punya ripple effect bawaan saat onTap diisi. Jangan bungkus dengan InkWell lagi, efeknya akan dobel dan terlihat aneh.

Kustomisasi yang Sering Dibutuhkan

  • shape: ubah bentuk kartu, misalnya RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)) untuk sudut lebih membulat.
  • color: warna latar kartu (default-nya mengikuti tema aplikasi).
  • dense: true di ListTile: membuat baris lebih ramping untuk daftar yang padat.
  • trailing tidak harus ikon: bisa Text harga, Switch, atau Checkbox untuk pengaturan.

Tips: Garis Pemisah Antar Baris

Kalau daftar tidak dibungkus Card per baris (misalnya daftar pengaturan), baris-baris ListTile bisa terlihat menyatu. Pakai ListTile.divideTiles untuk menyisipkan garis pemisah otomatis:

dart
ListView(
  children: ListTile.divideTiles(
    context: context,
    tiles: const [
      ListTile(leading: Icon(Icons.person), title: Text('Profil')),
      ListTile(leading: Icon(Icons.lock), title: Text('Keamanan')),
      ListTile(leading: Icon(Icons.info), title: Text('Tentang')),
    ],
  ).toList(),
)

Garisnya mengikuti warna tema (dividerColor) sehingga selalu selaras dengan mode terang dan gelap aplikasi.

Kesalahan Umum Pemula

1. Membungkus ListTile dengan InkWell/GestureDetector

dart
// SALAH: ripple dobel, kode mubazir
InkWell(
  onTap: () {},
  child: const ListTile(title: Text('Profil')),
)

// BENAR: ListTile sudah menangani sentuhan sendiri
ListTile(
  title: const Text('Profil'),
  onTap: () {},
)

2. onTap dikosongkan tapi mengharapkan ripple. Kalau onTap bernilai null, ListTile tampil nonaktif: tidak ada ripple dan teks terlihat pudar. Itu memang disengaja untuk status "disabled". Kalau baris harus bisa ditekan, selalu isi onTap dengan fungsi, walau masih kosong.

3. Card menempel tanpa margin di dalam Column. Card punya margin default 4px, tapi di dalam Column sempit itu sering kurang. Tambahkan margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6) agar kartu punya ruang napas, atau lebih baik: pakai ListView dengan padding seperti contoh di atas.

Tantangan

Daftar menu

Buat 3 Card berisi ListTile menu: Kopi Hitam (Rp10.000), Kopi Susu (Rp15.000), Matcha (Rp18.000). Masing-masing dengan leading icon coffee dan trailing teks harga.