Dasar-dasar tata letak Compose

Jetpack Compose mempermudah desain dan pembuatan UI aplikasi Anda. Compose mengubah status menjadi elemen UI, melalui:

  1. Komposisi elemen
  2. Tata letak elemen
  3. Gambar elemen

Compose mengubah status ke UI melalui komposisi, tata letak, gambar

Dokumen ini berfokus pada tata letak elemen, yang menjelaskan beberapa elemen penyusun yang disediakan Compose untuk membantu Anda menata elemen UI.

Sasaran tata letak di Compose

Implementasi Jetpack Compose sistem tata letak memiliki dua sasaran utama:

Dasar-dasar Fungsi yang dapat dikomposisi

Fungsi yang dapat dikomposisi adalah elemen dasar penyusun Compose. Fungsi yang dapat dikomposisi adalah fungsi yang menampilkan Unit yang mendeskripsikan beberapa bagian UI Anda. Fungsi ini mengambil beberapa input dan menghasilkan apa yang ditampilkan di layar. Untuk mengetahui informasi selengkapnya tentang composable, lihat dokumentasi Model mental Compose.

Fungsi yang dapat dikomposisi dapat membuat beberapa elemen UI. Namun, jika Anda tidak memberikan panduan tentang cara mengaturnya, Compose mungkin akan mengatur elemen dengan cara yang tidak Anda sukai. Misalnya, kode ini menghasilkan dua elemen teks:

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

Tanpa panduan tentang cara Anda ingin mengaturnya, Compose akan menumpuk elemen teks di atas satu sama lain, sehingga tidak dapat dibaca:

Dua elemen teks digambar di atas satu sama lain, membuat teks tidak dapat dibaca

Compose menyediakan kumpulan tata letak yang siap pakai untuk membantu Anda mengatur elemen UI, dan memudahkan Anda menentukan tata letak sendiri yang lebih khusus.

Komponen tata letak standar

Sering kali, Anda cukup menggunakan elemen tata letak standar Compose.

Gunakan Column untuk menempatkan item secara vertikal di layar.

@Composable
fun ArtistCard() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

Dua elemen teks disusun dalam tata letak kolom, sehingga teks dapat dibaca

Demikian pula, gunakan Row untuk menempatkan item secara horizontal di layar. Baik Column maupun Row mendukung konfigurasi penyelarasan elemen di dalamnya.

@Composable
fun ArtistCard(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(/*...*/)
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

Menampilkan tata letak yang lebih kompleks, dengan grafik kecil di samping kolom elemen teks

Gunakan Box untuk menempatkan elemen di atas elemen lainnya. Box juga mendukung konfigurasi perataan tertentu dari elemen yang ada di dalamnya.

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(/*...*/)
        Icon(/*...*/)
    }
}

Menampilkan dua elemen yang bertumpuk satu sama lain

Terkadang, hanya elemen penyusun inilah yang Anda butuhkan. Anda dapat menulis fungsi yang dapat dikomposisi sendiri untuk menggabungkan tata letak ini ke dalam tata letak yang lebih rumit, yang sesuai dengan aplikasi Anda.

Membandingkan tiga komponen tata letak sederhana: kolom, baris, dan kotak

Untuk menyetel posisi turunan dalam Row, setel argumen horizontalArrangement dan verticalAlignment. Untuk Column, setel argumen verticalArrangement dan horizontalAlignment:

@Composable
fun ArtistCard(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

Item diratakan di sisi kanan

Model tata letak

Pada model tata letak, hierarki UI ditata dalam satu penerusan. Setiap node pertama-tama diminta untuk mengukur dirinya sendiri, lalu mengukur setiap turunan secara rekursif, yang meneruskan batasan ukuran ke hierarki bawah pada turunan. Lalu, node daun diukur dan ditempatkan, dengan ukuran dan petunjuk penempatan yang telah di-resolve diteruskan kembali ke hierarki atas.

Singkatnya, induk diukur sebelum turunannya, tetapi mendapatkan pengubahan ukuran dan penempatan setelah turunannya.

Perhatikan fungsi SearchResult berikut.

@Composable
fun SearchResult(...) {
  Row(...) {
    Image(...)
    Column(...) {
      Text(...)
      Text(..)
    }
  }
}

Fungsi ini menghasilkan hierarki UI berikut.

SearchResult
  Row
    Image
    Column
      Text
      Text

Dalam contoh SearchResult, tata letak hierarki UI mengikuti urutan ini:

  1. Node root Row diminta untuk mengukur.
  2. Node root Row meminta pengukuran turunan pertamanya, Image.
  3. Image adalah node daun (yaitu yang tidak memiliki turunan), sehingga melaporkan ukuran dan menampilkan petunjuk penempatan.
  4. Node root Row meminta pengukuran turunan keduanya, Column.
  5. Node Column meminta pengukuran turunan Text pertamanya.
  6. Node Text pertama adalah node daun, sehingga melaporkan ukuran dan menampilkan petunjuk penempatan.
  7. Node Column meminta pengukuran turunan keduanya, Text.
  8. Node Text kedua adalah node daun, sehingga melaporkan ukuran dan menampilkan petunjuk penempatan.
  9. Setelah node Column menyelesaikan pengukuran, pengubahan ukuran, dan menempatkan turunannya, node tersebut dapat menentukan ukuran dan penempatannya sendiri.
  10. Setelah node root Row menyelesaikan pengukuran, pengubahan ukuran, dan menempatkan turunannya, node root tersebut dapat menentukan ukuran dan penempatannya sendiri.

Urutan pengukuran, pengubahan ukuran, dan penempatan di hierarki UI Hasil Penelusuran

Performa

Compose mencapai performa tinggi dengan mengukur turunan hanya satu kali. Pengukuran single-pass baik untuk performa, memungkinkan Compose menangani hierarki UI yang dalam secara efisien. Jika elemen mengukur turunannya dua kali dan turunan itu mengukur setiap turunannya dua kali dan seterusnya, satu upaya untuk membuat tata letak seluruh UI harus melakukan banyak pekerjaan, sehingga sulit untuk menjaga aplikasi tetap berperforma tinggi.

Jika tata letak Anda memerlukan beberapa pengukuran karena alasan tertentu, Compose menawarkan sistem khusus, yaitu pengukuran intrinsik. Anda dapat membaca fitur ini lebih lanjut di Pengukuran intrinsik di tata letak Compose.

Karena pengukuran dan penempatan adalah sub-fase penerusan tata letak yang berbeda, setiap perubahan yang hanya memengaruhi penempatan item, bukan pengukuran, dapat dijalankan secara terpisah.

Menggunakan pengubah dalam tata letak

Seperti yang dibahas dalam Pengubah Compose, Anda dapat menggunakan pengubah untuk mendekorasi atau meningkatkan composable. Pengubah sangat penting untuk menyesuaikan tata letak. Misalnya, di sini kita membuat rantai beberapa pengubah untuk menyesuaikan ArtistCard:

@Composable
fun ArtistCard(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(elevation = 4.dp) { /*...*/ }
    }
}

Tata letak yang lebih kompleks lagi, menggunakan pengubah untuk mengubah cara mengatur grafik dan area mana yang merespons input pengguna

Dalam kode di atas, perhatikan berbagai fungsi pengubah yang digunakan bersama-sama.

  • clickable membuat composable bereaksi terhadap input pengguna dan menampilkan ripple.
  • padding menempatkan ruang di sekitar elemen.
  • fillMaxWidth membuat fungsi yang dapat dikomposisi mengisi lebar maksimum yang diberikan kepadanya dari induknya.
  • size() menentukan lebar dan tinggi yang dipilih untuk elemen.

Tata letak yang dapat di-scroll

Pelajari lebih lanjut tata letak yang dapat di-scroll dalam Dokumentasi gestur Compose.

Untuk daftar dan daftar lambat, lihat Dokumentasi daftar Compose.

Layout responsif

Tata letak harus didesain dengan mempertimbangkan berbagai orientasi layar dan ukuran faktor bentuk. Compose menawarkan beberapa mekanisme untuk memfasilitasi penyesuaian tata letak composable ke berbagai konfigurasi layar.

Batasan

Untuk mengetahui batasan yang berasal dari induk dan mendesain tata letak yang sesuai, Anda dapat menggunakan BoxWithConstraints. Batasan pengukuran dapat ditemukan dalam cakupan lambda konten. Anda dapat menggunakan batasan pengukuran ini untuk membuat tata letak yang berbeda untuk konfigurasi layar yang berbeda:

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

Tata letak berbasis slot

Compose menyediakan berbagai macam composable berdasarkan Desain Material dengan dependensi androidx.compose.material:material (disertakan saat membuat project Compose di Android Studio) agar proses mem-build UI menjadi lebih mudah. Elemen seperti Drawer, FloatingActionButton, dan TopAppBar semuanya disediakan.

Komponen material banyak menggunakan API slot, yakni sebuah pola yang dibuat Compose untuk menghadirkan lapisan penyesuaian di atas composable. Pendekatan ini membuat komponen lebih fleksibel, karena menerima elemen turunan yang dapat mengonfigurasi sendiri, bukan harus menampilkan setiap parameter konfigurasi turunan. Slot memberikan ruang kosong di UI untuk diisi developer, sesuai keinginan mereka. Misalnya, ini adalah slot yang dapat Anda sesuaikan dalam TopAppBar:

Diagram yang menampilkan slot yang tersedia di panel aplikasi Komponen Material

Komponen biasanya menggunakan lambda komponen content ( content: @Composable () -> Unit). API slot menampilkan beberapa parameter content untuk penggunaan tertentu. Misalnya, TopAppBar memungkinkan Anda menyediakan konten untuk title, navigationIcon, dan actions.

Misalnya, Scaffold memungkinkan Anda menerapkan UI dengan struktur tata letak Desain Material dasar. Scaffold menyediakan slot untuk komponen Material tingkat atas yang paling umum, seperti TopAppBar, BottomAppBar, FloatingActionButton, dan Drawer. Dengan menggunakan Scaffold, sangat mudah untuk memastikan komponen ini diposisikan dengan benar dan bekerja bersama dengan benar.

Contoh aplikasi JetNews, yang menggunakan Scaffold untuk memosisikan beberapa elemen

@Composable
fun HomeScreen(/*...*/) {
    Scaffold(
        drawerContent = { /*...*/ },
        topBar = { /*...*/ },
        content = { /*...*/ }
    )
}