Google berkomitmen untuk mendorong terwujudnya keadilan ras bagi komunitas Kulit Hitam. Lihat caranya.

Tata letak dalam Compose

Jetpack Compose mempermudah desain dan pembuatan UI aplikasi Anda. Dokumen ini menjelaskan beberapa elemen penyusun yang disediakan Compose untuk membantu Anda mengatur elemen UI, dan menunjukkan cara membuat tata letak yang lebih khusus saat Anda memerlukannya.

Fungsi yang dapat dikomposisi adalah elemen dasar penyusun Compose. Fungsi yang dapat dikomposisi adalah fungsi yang mendeskripsikan beberapa bagian UI Anda. Fungsi ini mengambil beberapa input dan menghasilkan apa yang ditampilkan di layar. Untuk informasi selengkapnya tentang komponen, 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 nilai penting elemen di dalamnya.

@Composable
fun ArtistCard(artist: Artist) {
    Row(verticalGravity = 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 Stack untuk menempatkan satu elemen di atas elemen lainnya.

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

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

Masing-masing tata letak dasar ini menentukan setelan nilai pentingnya sendiri, menentukan cara elemen-elemen harus diatur. Untuk mengonfigurasi elemen ini, gunakan pengubah.

Pengubah

Pengubah memungkinkan Anda menyesuaikan cara menyajikan sebuah komponen. Pengubah memungkinkan Anda melakukan hal-hal berikut:

  • Mengubah perilaku dan tampilan komponen
  • Menambahkan informasi, seperti label aksesibilitas
  • Memproses input pengguna
  • Menambahkan interaksi tingkat tinggi, seperti membuat elemen yang dapat diklik, dapat di-scroll, dapat ditarik, atau dapat di-zoom

Pengubah adalah objek Kotlin standar. Buat pengubah dengan memanggil salah satu fungsi class Modifier. Anda dapat menggabungkan fungsi-fungsi berikut untuk menyusunnya:

@Composable
fun ArtistCard(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalGravity = Alignment.CenterVertically) { … }
        Spacer(Modifier.preferredSize(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 komponen bereaksi terhadap input pengguna.
  • padding() menempatkan ruang di sekitar elemen.
  • fillMaxWidth() membuat komponen mengisi lebar maksimum yang diberikan kepadanya dari induknya.
  • preferredSize() menentukan lebar dan tinggi yang dipilih untuk elemen.

Urutan fungsi pengubah sangat signifikan. Karena setiap fungsi melakukan perubahan pada Modifier yang ditampilkan oleh fungsi sebelumnya, urutan tersebut akan memengaruhi hasil akhir. Mari kita lihat contohnya:

@Composable
fun ArtistCard(...) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

Seluruh area, termasuk padding di sekitar tepinya, merespons klik

Pada kode di atas, seluruh area dapat diklik, termasuk padding di sekitarnya, karena pengubah padding telah diterapkan setelah pengubah clickable. Jika pengubah diterapkan dalam urutan lain, ruang yang ditambahkan oleh padding tidak akan bereaksi terhadap input pengguna:

@Composable
fun ArtistCard(...) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

Padding di sekitar tepi tata letak tidak lagi merespons klik

Tata letak yang dapat di-scroll

Gunakan ScrollableRow atau ScrollableColumn untuk membuat elemen di dalam scroll Row atau Column.

@Composable
fun Feed(
  feedItems: List<Artist>,
  onSelected: (Artist) -> Unit
) {
  ScrollableColumn(Modifier.fillMaxSize()) {
    feedItems.forEach {
      ArtistCard(it, onSelected(it))
    }
  }
}

Beberapa tata letak serupa dalam kolom yang dapat di-scroll

Pendekatan ini berfungsi dengan baik jika ada beberapa elemen untuk ditampilkan, tetapi dapat dengan cepat menjadi masalah performa untuk set data yang besar. Untuk menampilkan sebagian saja elemen yang terlihat di layar, gunakan LazyColumnFor atau LazyRowFor.

@Composable
fun Feed(
  feedItems: List<Artist>,
  onSelected: (Artist) -> Unit
) {
  Surface(Modifier.fillMaxSize()) {
    LazyColumnFor(feedItems) { item ->
      ArtistCard(item, onSelected(item))
    }
  }
}

Komponen Material Bawaan

Tingkat abstraksi UI tertinggi untuk Compose adalah Desain Material. Compose menyediakan berbagai macam komponen siap pakai yang memudahkan pembuatan UI. Elemen seperti Drawer, FloatingActionButton, dan TopAppBar, semuanya disediakan.

Komponen material banyak menggunakan API slot, sebuah pola yang dibuat Compose untuk menghadirkan lapisan penyesuaian di atas komponen. Slot memberikan ruang kosong di UI untuk diisi developer, sesuai keinginan mereka. Misalnya, ini adalah slot yang dapat Anda sesuaikan dalam TopAppBar:

Menampilkan slot di panel aplikasi, tempat Anda dapat menambahkan elemen UI

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.

Komponen tingkat tertinggi dengan Material adalah Scaffold. 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.

Menampilkan tata letak yang menggunakan Scaffold untuk mengatur elemen dengan cara yang konsisten dengan Desain Material

@Composable
fun HomeScreen(...) {
    Scaffold (
        drawerContent = { ... },
        topBar = { ... },
        bodyContent = { ... }
    )
}

ConstraintLayout

ConstraintLayout dapat membantu menempatkan komponen relatif terhadap yang lain di layar, dan merupakan alternatif untuk menggunakan beberapa elemen Row, Column, dan Stack. ConstraintLayout berguna saat menerapkan tata letak yang lebih besar dengan persyaratan perataan yang lebih rumit.

ConstraintLayout di Compose berfungsi dengan DSL:

  • Referensi dibuat menggunakan createRefs() atau createRefFor(), dan setiap komponen dalam ConstraintLayout harus memiliki referensi yang terkait dengannya.
  • Batasan diberikan menggunakan pengubah constrainAs(), yang menggunakan referensi sebagai parameter dan memungkinkan Anda menentukan batasannya di lambda bodi.
  • Batasan ditentukan menggunakan linkTo() atau metode berguna lainnya.
  • parent adalah referensi yang sudah ada dan dapat digunakan untuk menentukan batasan terhadap komponen ConstraintLayout itu sendiri.

Berikut ini contoh komponen yang menggunakan ConstraintLayout:

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout {
        // Create references for the composables to constrain
        val (button, text) = createRefs()

        Button(
            onClick = { /* Do something */ },
            // Assign reference "button" to the Button composable
            // and constrain it to the top of the ConstraintLayout
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("Button")
        }

        // Assign reference "text" to the Text composable
        // and constrain it to the bottom of the Button composable
        Text("Text", Modifier.constrainAs(text) {
            top.linkTo(button.bottom, margin = 16.dp)
        })
    }
}

Kode ini membatasi bagian atas Button untuk induk dengan margin 16.dp dan Text di bagian bawah Button, juga dengan margin 16.dp.

Menampilkan tombol dan elemen teks yang disusun dalam ConstraintLayout

Untuk contoh selengkapnya tentang cara bekerja dengan ConstraintLayout, cobalah codelab tata letak.

API terpisah

Pada contoh ConstraintLayout, batasan ditentukan sebagai bagian dari tata letak, dengan pengubah dalam komponen yang menerapkannya. Namun, pada beberapa situasi, lebih baik memisahkan batasan itu dari tata letak yang diterapkan. Misalnya, Anda mungkin ingin mengubah batasan berdasarkan konfigurasi layar, atau membuat animasi di antara dua kumpulan batasan.

Untuk kasus seperti ini, Anda dapat menggunakan ConstraintLayout dengan cara lain:

  1. Teruskan ConstraintSet sebagai parameter untuk ConstraintLayout.
  2. Tetapkan referensi yang dibuat di ConstraintSet untuk komponen menggunakan pengubah tag.
@Composable
fun DecoupledConstraintLayout() {
    WithConstraints {
        val constraints = if (minWidth < 600.dp) {
            decoupledConstraints(margin = 16.dp) // Portrait constraints
        } else {
            decoupledConstraints(margin = 32.dp) // Landscape constraints
        }

        ConstraintLayout(constraints) {
            Button(
                onClick = { /* Do something */ },
                modifier = Modifier.tag("button")
            ) {
                Text("Button")
            }

            Text("Text", Modifier.tag("text"))
        }
    }
}

private fun decoupledConstraints(margin: Dp): ConstraintSet {
    return ConstraintSet2 {
        val button = createRefFor("button")
        val text = createRefFor("text")

        constrain(button) {
            top.linkTo(parent.top, margin= margin)
        }
        constrain(text) {
            top.linkTo(button.bottom, margin)
        }
    }
}

Kemudian, saat Anda perlu mengubah batasan, Anda dapat meneruskan ConstraintSet yang berbeda.

Tata letak kustom

Beberapa fungsi yang dapat dikomposisi membuat sepotong UI saat dipanggil, yang kemudian ditambahkan ke hierarki UI yang dirender di layar. Setiap elemen UI memiliki satu induk dan kemungkinan banyak turunan. Setiap elemen juga memiliki lokasi dalam induknya, ditentukan sebagai posisi (x, y), dan ukuran, yang ditetapkan sebagai width dan height.

Elemen diminta untuk menentukan Batasan mereka sendiri yang harus dipenuhi. Batasan membatasi width dan height minimum dan maksimum dari sebuah elemen. Jika elemen memiliki elemen turunan, induk dapat mengukur setiap turunan untuk membantu menentukan ukuran induk. Setelah elemen melaporkan ukurannya sendiri, elemen memiliki peluang untuk menempatkan elemen turunannya relatif terhadap elemennya sendiri, seperti yang dijelaskan secara mendetail dalam Membuat tata letak kustom.

Pengukuran single-pass baik untuk performa, memungkinkan Compose menangani hierarki UI yang dalam secara efisien. Jika elemen tata letak mengukur turunannya dua kali dan turunan itu mengukur salah satu turunannya dua kali dan seterusnya, satu upaya untuk membuat tata letak seluruh UI harus melakukan banyak pekerjaan, sehingga sulit untuk menjaga performa aplikasi Anda tetap baik. Namun, ada kalanya Anda memerlukan informasi tambahan selain apa yang didapatkan oleh pengukuran satu turunan. Ada beberapa pendekatan yang dapat secara efisien mengatasi situasi seperti itu, yang akan dibahas di Menggunakan pengubah tata letak.

Menggunakan pengubah tata letak

Anda dapat menggunakan pengubah layout untuk mengubah cara pengukuran dan tata letak komponen. Layout adalah lambda; parameternya mencakup komponen yang dapat diukur, diteruskan sebagai measurable, dan batasan komponen tersebut yang diteruskan, sebagai constraints. Sebagian besar pengubah layout kustom mengikuti pola ini:

fun Modifier.customLayoutModifier(...) =
    Modifier.layout { measurable, constraints ->
  ...
})

Mari kita tampilkan Text di layar dan mengontrol jarak dari atas ke bagian dasar baris pertama teks. Untuk melakukannya, gunakan pengubah layout untuk menempatkan komponen secara manual di layar. Berikut adalah perilaku yang diinginkan saat padding atas Text ditetapkan 24.dp:

Menampilkan perbedaan antara padding UI normal, yang menyetel spasi antara elemen, dan padding teks yang menyetel ruang dari satu bagian dasar ke bagian dasar berikutnya

Inilah kode untuk menghasilkan ruang tersebut:

fun Modifier.firstBaselineToTop(
  firstBaselineToTop: Dp
) = Modifier.layout { measurable, constraints ->
  // Measure the composable
  val placeable = measurable.measure(constraints)

  // Check the composable has a first baseline
  check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
  val firstBaseline = placeable[FirstBaseline]

  // Height of the composable with padding - first baseline
  val placeableY = firstBaselineToTop.toIntPx() - firstBaseline
  val height = placeable.height + placeableY
  layout(placeable.width, height) {
    // Where the composable gets placed
    placeable.placeRelative(0, placeableY)
  }
})

Inilah yang terjadi dalam kode tersebut:

  1. Di parameter lambda measurable, Anda mengukur Text dengan memanggil measurable.measure(constraints).
  2. Anda menentukan ukuran komponen dengan memanggil metode layout(width, height), yang juga memberikan lambda yang digunakan untuk menempatkan turunan. Dalam hal ini, tempatnya adalah tinggi antara bagian dasar terakhir dan padding atas yang ditambahkan.
  3. Anda dapat memosisikan turunan di layar dengan memanggil placeable.placeRelative(x, y). Jika turunan tidak ditempatkan, turunan itu tidak akan terlihat. Posisi y sesuai dengan padding atas - yaitu posisi bagian dasar pertama teks. placeRelative secara otomatis mencerminkan posisi dalam konteks kanan-ke-kiri.

Untuk memverifikasi bahwa tindakan ini berfungsi sebagaimana diharapkan, gunakan pengubah ini pada Text:

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
  MyApplicationTheme {
    Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
  }
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
  MyApplicationTheme {
    Text("Hi there!", Modifier.padding(top = 32.dp))
  }
}

Beberapa pratinjau elemen teks; yang satu menampilkan padding biasa di antara elemen, yang lain menampilkan padding dari satu bagian dasar ke bagian dasar berikutnya

Membuat tata letak kustom

Pengubah layout hanya mengubah satu komponen. Untuk mengontrol beberapa komponen secara manual, gunakan komponen Layout sebagai gantinya. Komponen ini memungkinkan Anda mengukur dan menata letak turunan secara manual. Semua tata letak dengan tingkat lebih tinggi seperti Column dan Row dibuat dengan komponen Layout.

Mari kita buat implementasi sederhana Column. Sebagian besar tata letak kustom mengikuti pola ini:

@Composable
fun MyOwnColumn(
    modifier: Modifier = Modifier,
    content: @Composable() () -> Unit
) {
    Layout(
        modifier = modifier,
        children = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
    }
}

Demikian pula dengan pengubah layout, measurables adalah daftar turunan yang perlu diukur dan constraints adalah batasan yang diteruskan ke Layout. Dengan logika yang sama seperti sebelumnya, MyOwnColumn dapat diterapkan seperti ini:

@Composable
fun MyOwnColumn(
    modifier: Modifier = Modifier,
    children: @Composable() () -> Unit
) {
    Layout(
        modifier = modifier,
        children = children
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Track the y co-ord we have placed children up to
            var yPosition = 0

            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)

                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}

Komponen turunan dibatasi oleh batasan Layout, dan turunan tersebut ditempatkan berdasarkan yPosition dari komponen sebelumnya.

Berikut ini cara penggunaan komponen kustom tersebut:

@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
    MyOwnColumn(modifier.padding(8.dp)) {
        Text("MyOwnColumn")
        Text("places items")
        Text("vertically.")
        Text("We've done it by hand!")
    }
}

alt_text

Arah tata letak

Ubah arah tata letak komponen menggunakan ambient LayoutDirection.

Jika Anda menempatkan komponen secara manual di layar, LayoutDirection adalah bagian dari LayoutScope dari pengubah layout atau komponen Layout.

Saat menggunakan layoutDirection, tempatkan komponen menggunakan place. Tidak seperti metode placeRelative, place tidak berubah berdasarkan arah membaca (dari kiri ke kanan versus kanan ke kiri).

Pelajari lebih lanjut

Untuk mempelajari lebih lanjut, cobalah Tata letak di codelab Jetpack Compose.