Layout adaptif

Menyesuaikan tampilan untuk ponsel, tablet, desktop, dan web dengan layout adaptif.

Daftar isi

Aplikasi Flutter berjalan pada layar dengan lebar yang berkisar dari ponsel kecil hingga monitor desktop. Topik ini membahas layout adaptif (tata letak yang menyesuaikan diri dengan ruang yang tersedia) menggunakan MediaQuery, LayoutBuilder, dan breakpoint, yaitu ambang lebar tempat tata letak berubah. Pembahasan juga memuat pendekatan widget yang menata ulang diri alih-alih memperkecil ukurannya, ditutup dengan demo interaktif yang meniru perilaku LayoutBuilder. Setelah topik ini dibaca, satu layar aplikasi dapat dibuat agar tetap layak dibaca pada ponsel, tablet, desktop, dan web.

Menilai ukuran layar dengan MediaQuery#

MediaQuery memberi tahu widget tentang karakteristik layar tempat widget itu berada. Pemanggilan MediaQuery.of(context) mengembalikan objek yang memuat ukuran jendela, orientasi, dan ukuran area aman seperti notch pada ponsel. Dua properti yang paling sering digunakan untuk keputusan tata letak adalah size dan orientation.

dart Contoh: membaca ukuran layar dari MediaQuery
// tidak-dijalankan
Widget build(BuildContext context) {
  final ukuran = MediaQuery.of(context).size;

  if (ukuran.width >= 600) {
    return const TataLetakLebar();
  }
  return const TataLetakSempit();
}

MediaQuery mengukur seluruh jendela aplikasi. Nilainya cocok untuk keputusan pada pangkal pohon widget, tetapi kurang tepat untuk widget yang digunakan ulang di dalam panel, dialog, atau kolom dengan lebar berbeda, karena lebar jendela tidak selalu sama dengan lebar ruang yang tersedia bagi widget itu.

Nilai yang dibaca juga berubah secara otomatis. MediaQuery bekerja sebagai inherited widget, yaitu widget yang menyiarkan perubahan kepada seluruh keturunannya, sehingga perputaran perangkat dan perubahan ukuran jendela memicu pemanggilan ulang metode build tanpa kode tambahan. Perilaku ini menjelaskan mengapa keputusan tata letak cukup ditulis sekali di dalam metode build, lalu tampilan menyesuaikan diri setiap kali ruang berubah. Orientasi pun tersedia pada objek yang sama, sehingga perbedaan potret dan lanskap tidak perlu dideteksi secara manual.

Constraint lokal dengan LayoutBuilder#

LayoutBuilder menyelesaikan persoalan tersebut dengan memberikan constraint (batasan ukuran) lokal kepada builder-nya. Alih-alih bertanya pada layar, widget bertanya pada ruang yang benar-benar diberikan kepadanya. Builder dipanggil ulang setiap kali batasannya berubah, misalnya saat pengguna memutar perangkat, mengubah ukuran jendela desktop, atau membuka panel samping.

dart Contoh: LayoutBuilder dengan dua tata letak
// tidak-dijalankan
LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth >= 600) {
      return const TataLetakLebar();
    }
    return const TataLetakSempit();
  },
)

Pola ini paling mendekati cara berpikir layout adaptif karena keputusan diambil dari ruang yang tersedia, bukan dari jenis perangkat. Widget yang ditulis dengan pola ini bekerja dengan benar di mana pun widget itu ditempatkan.

Perbedaan MediaQuery dan LayoutBuilder terlihat pada contoh nyata. Widget pencatat yang sama ditempatkan di dalam dialog sempit dan di dalam panel samping yang lebar akan menerima constraint yang berbeda pula, sehingga LayoutBuilder mengganti tata letaknya sesuai tempatnya digunakan, sedangkan keputusan dari MediaQuery tetap sama di kedua tempat. LayoutBuilder juga dapat diletakkan bertingkat karena setiap builder hanya membaca batasan yang diterimanya sendiri.

Breakpoint konvensional#

Flutter tidak menetapkan angka breakpoint wajib, sedangkan dokumentasi resmi memberikan rentang yang lazim digunakan dan sejalan dengan panduan Material. Tabel berikut merangkum rentang tersebut dalam dp (density-independent pixel, satuan yang ukurannya setara pada semua perangkat).

Rentang lebarKelas perangkat khasPendekatan tata letak
kurang dari 600 dpponselsatu kolom
600 sampai 839 dptablet dan perangkat lipat terbukadua kolom
840 dp atau lebihdesktop dan layar lebarpanel ganda hingga multi panel

Perangkat lipat menambah kerumitan kecil karena lebar yang terukur berubah saat perangkat dilipat atau dibuka, tetapi angka breakpoint yang sama tetap berlaku karena keputusannya berbasis lebar. Nilai ambang juga sebaiknya ditulis sebagai konstanta bersama agar seluruh layar menggunakan angka yang sama.

Widget yang menata ulang diri#

Prinsip inti layout adaptif adalah mengganti susunan widget, bukan mengecilkan ukurannya. Teks yang diperkecil hingga tidak terbaca dan tombol yang menyusut adalah tanda tata letak yang salah arah. Pendekatan yang benar memindahkan isi ke bentuk yang cocok, yaitu daftar satu kolom menjadi grid dua kolom, kolom menjadi baris, dan konten tambahan berpindah ke panel samping.

Contoh berikut memilih antara ListView dan GridView untuk daftar tugas. ListView.builder tetap digunakan pada layar sempit mengikuti sifat lazy-nya, sedangkan layar lebar menggunakan grid dua kolom agar ruang horizontal terpakai.

dart Contoh: daftar tugas yang menata ulang diri
// tidak-dijalankan
class DaftarTugas extends StatelessWidget {
  const DaftarTugas({super.key, required this.tugas});

  final List<String> tugas;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth >= 600) {
          return GridView.count(
            crossAxisCount: 2,
            children: [for (final item in tugas) KartuTugas(item)],
          );
        }
        return ListView.builder(
          itemCount: tugas.length,
          itemBuilder: (context, index) => KartuTugas(tugas[index]),
        );
      },
    );
  }
}

Struktur yang sama berlaku untuk web dan desktop. Layar lebar yang memuat ListView selebar layar memboroskan ruang di kedua sisinya, sedangkan grid atau panel ganda memanfaatkannya.

Pergantian bentuk tidak terbatas pada daftar. Widget yang sama dapat menggunakan Column pada layar sempit dan Row pada layar lebar, misalnya tombol simpan dan tombol batal yang bertumpuk di ponsel tetapi berdampingan di desktop. Yang penting dijaga adalah isi dan logikanya tetap satu sumber, sehingga yang berganti hanyalah penataannya.

Mencoba ambang lebar pada demo#

Demo berikut meniru perilaku LayoutBuilder pada satu widget. Seret penggeser untuk mengubah lebar area, lalu amati kartu catatan yang berpindah dari satu kolom ke dua kolom pada ambang 480 piksel. Angka ambang pada demo dipilih lebih kecil daripada breakpoint pada tabel di atas agar perubahan tetap terlihat dalam rentang penggeser.

Mekanisme di baliknya sama dengan pola LayoutBuilder: setiap perubahan lebar menghitung ulang keputusan tata letak, dan susunan widget diganti, bukan diperkecil. Saat penggeser berhenti pada posisi mana pun selain tepat pada ambang, tata letak tidak berubah sedikit pun, karena keputusan hanya diambil ketika ambang dilewati. Hal inilah yang membedakannya dari penyesuaian persentase yang mengubah ukuran konten pada setiap perubahan kecil. Perhatikan pula isi kartu yang tidak berubah sama sekali, karena yang diganti hanyalah wadah yang menatanya.

Kesalahan umum#

Lima kekeliruan yang sering muncul saat menulis layout adaptif:

  • Menghitung breakpoint dari MediaQuery di dalam widget yang digunakan ulang. Nilainya merujuk jendela, bukan ruang lokal, sehingga keputusannya keliru di dalam panel atau dialog. Gunakan LayoutBuilder untuk komponen.
  • Mengasumsikan jenis perangkat dari lebar layar. Ponsel berorientasi lanskap dapat lebih lebar daripada tablet potret, sehingga keputusan harus berbasis lebar, bukan jenis perangkat.
  • Membuat konten melebar tanpa batas pada desktop. Baris teks yang terlalu panjang sulit dibaca, sehingga layar lebar tetap perlu membatasi lebar maksimum kontennya.
  • Menghitung ulang tata letak pada setiap piksel. Keputusan tata letak cukup diambil pada breakpoint, bukan pada setiap perubahan ukuran kecil.
  • Menyebarkan angka ambang di banyak widget tanpa daftar tunggal. Ambang yang berbeda di tiap tempat membuat layar berperilaku tidak konsisten, sehingga breakpoint sebaiknya dikumpulkan sebagai konstanta bersama.

Ringkasan#

  • MediaQuery memberi ukuran jendela dan orientasi, sedangkan LayoutBuilder memberikan constraint lokal, dan LayoutBuilder adalah pilihan yang tepat untuk widget yang digunakan ulang.
  • Breakpoint konvensional berada pada 600 dp dan 840 dp untuk memisahkan ponsel, tablet, dan layar lebar.
  • Tata letak adaptif mengganti susunan widget, misalnya ListView menjadi GridView, alih-alih memperkecil ukuran teks dan tombol.

Referensi#