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.

Sasaran tata letak di Compose

Implementasi Jetpack Compose dari sistem tata letak memiliki dua sasaran utama: menjadi berperforma tinggi, dan memudahkan penulisan tata letak kustom. Performa tinggi dicapai di Compose dengan melarang pengukuran tata letak turunan lebih dari sekali. Jika beberapa pengukuran diperlukan, Compose memiliki sistem khusus yang siap digunakan yaitu pengukuran intrinsik. Anda dapat membaca selengkapnya tentang fitur ini di bagian pengukuran intrinsik.

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 perataan 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 satu elemen di atas elemen lainnya.

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

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.

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

Pengubah

Pengubah memungkinkan Anda mendekorasi atau meningkatkan composable. Pengubah memungkinkan Anda melakukan hal-hal berikut:

  • Mengubah ukuran, tata letak, perilaku, dan tampilan yang dapat dikomposisi
  • 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 mengomposisikannya:

@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.

Pentingnya urutan pengubah

Urutan fungsi pengubah 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 urutan pengubah dibalik, ruang yang ditambahkan oleh padding tidak akan merespons 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

Pengubah bawaan

Jetpack Compose menyediakan daftar pengubah bawaan untuk membantu Anda mendekorasi atau meningkatkan composable. Beberapa pengubah seperti padding, clickable, dan fillMaxWidth telah diperkenalkan. Berikut daftar pengubah umum lainnya:

ukuran

Secara default, tata letak yang disediakan di Compose menggabungkan turunannya. Namun, Anda dapat menyetel ukuran dengan menggunakan pengubah size:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

Perhatikan bahwa ukuran yang Anda tentukan mungkin tidak diterapkan jika tidak memenuhi batasan yang berasal dari induk tata letak. Jika Anda mengharuskan ukuran composable diperbaiki, terlepas dari batasan yang masuk, gunakan pengubah requiredSize:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

Gambar turunan lebih besar daripada batasan yang berasal dari induknya

Dalam contoh ini, meski induk height disetel ke 100.dp, tinggi Image akan menjadi 150.dp, karena pengubah requiredSize lebih diutamakan.

Jika Anda ingin tata letak turunan mengisi semua tinggi yang tersedia yang diizinkan oleh induk, tambahkan pengubah fillMaxHeight (Compose juga menyediakan fillMaxSize dan fillMaxWidth):

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

Tinggi gambar sama dengan induknya

Jika Anda ingin menambahkan padding di atas dasar pengukuran teks sehingga Anda mencapai jarak tertentu dari bagian atas tata letak ke dasar pengukuran, gunakan pengubah paddingFromBaseline:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

Teks dengan padding di atasnya

Offset

Untuk menempatkan tata letak relatif ke posisi aslinya, tambahkan pengubah offset dan setel offset dalam sumbu x dan y. Offset bisa positif dan tidak positif. Perbedaan antara padding dan offset adalah penambahan offset pada composable tidak mengubah pengukurannya:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

Teks digeser ke sisi kanan penampung induknya

Pengubah offset diterapkan secara horizontal sesuai dengan arah tata letak. Dalam konteks kiri-ke-kanan, offset positif menggeser elemen ke kanan, sedangkan dalam konteks kanan-ke-kiri, elemen akan bergeser ke kiri. Jika Anda perlu menyetel offset tanpa mempertimbangkan arah tata letak, lihat pengubah absoluteOffset, ketika nilai offset positif selalu menggeser elemen ke kanan.

Keamanan jenis di Compose

Di Compose, ada pengubah yang hanya berfungsi saat diterapkan ke turunan composable tertentu. Misalnya, jika Anda ingin membuat turunan sebesar induk Box tanpa memengaruhi ukuran Box, gunakan pengubah matchParentSize.

Compose menerapkan keamanan jenis ini melalui cakupan kustom. Misalnya, matchParentSize hanya tersedia di BoxScope. Oleh karena itu, hanya dapat digunakan saat turunan digunakan dalam Box.

Pengubah cakupan memberi tahu induk tentang beberapa informasi turunan yang harus diketahui induk. Ini juga biasa disebut sebagai pengubah data induk. Internalnya berbeda dari pengubah tujuan umum, tetapi dari perspektif penggunaan, perbedaan ini tidak penting.

matchParentSize di Box

Seperti yang disebutkan di atas, jika Anda ingin tata letak turunan berukuran sama dengan Box induk tanpa memengaruhi ukuran Box, gunakan pengubah matchParentSize.

Perhatikan bahwa matchParentSize hanya tersedia dalam cakupan Box, artinya hanya berlaku untuk turunan langsung dari composable Box.

Dalam contoh di bawah ini, Spacer turunan mengambil ukurannya dari Box induknya, yang kemudian mengambil ukurannya dari turunan terbesar, dalam hal ini ArtistCard.

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(Modifier.matchParentSize().background(Color.LightGray))
        ArtistCard()
    }
}

Latar belakang abu-abu yang mengisi penampungnya

Jika fillMaxSize digunakan sebagai pengganti matchParentSize, Spacer akan mengambil semua ruang tersedia yang diizinkan untuk induk, yang pada akhirnya menyebabkan induk memperluas dan mengisi semua ruang yang tersedia.

Latar belakang abu-abu yang memenuhi layar

bobot dalam Baris dan Kolom

Seperti yang telah Anda lihat di bagian sebelumnya tentang Padding dan ukuran, secara default, ukuran composable ditentukan oleh konten yang digabungkan. Anda dapat menyetel ukuran composable agar fleksibel dalam induknya menggunakan Pengubah weight yang hanya tersedia di RowScope, dan ColumnScope.

Mari kita ambil Row yang berisi dua composable Box. Kotak pertama diberikan dua kali weight kotak kedua, sehingga diberi lebar dua kali lipat. Karena Row selebar 210.dp, Box pertama selebar 140.dp, dan kedua selebar 70.dp:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

Lebar gambar dua kali lebar teks

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 = { /*...*/ }
    )
}

ConstraintLayout

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

Untuk menggunakan ConstraintLayout di Compose, Anda perlu menambahkan dependensi ini di build.gradle:

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-alpha08"

ConstraintLayout di Compose berfungsi dengan DSL:

  • Referensi dibuat menggunakan createRefs() atau createRefFor(), dan setiap composable 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 body.
  • 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 ConstraintLayout contoh, batasan ditentukan sebagai bagian dari tata letak, dengan pengubah dalam composable yang diterapkan. 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 layoutId.
@Composable
fun DecoupledConstraintLayout() {
    BoxWithConstraints {
        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.layoutId("button")
            ) {
                Text("Button")
            }

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

private fun decoupledConstraints(margin: Dp): ConstraintSet {
    return ConstraintSet {
        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.

Mempelajari lebih lanjut

Pelajari ConstraintLayout di Compose lebih lanjut di bagian Tata Letak Batasan pada codelab Tata Letak di Jetpack Compose, dan lihat cara kerja API di Contoh Compose yang menggunakan ConstraintLayout.

Tata letak kustom

Dalam Compose, elemen UI diwakili oleh fungsi yang dapat dikomposisi yang memancarkan 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 berada dalam induknya, yang ditetapkan sebagai posisi (x, y), dan ukuran, yang ditetapkan sebagai width dan height.

Induk menentukan batasan untuk elemen turunannya. Sebuah elemen diminta untuk menentukan ukurannya dalam batasan tersebut. Batasan membatasi width dan height minimum dan maksimum dari sebuah elemen. Jika suatu elemen memiliki elemen turunan, elemen tersebut dapat mengukur setiap turunan untuk membantu menentukan ukurannya. Setelah elemen menentukan dan melaporkan ukurannya sendiri, elemen tersebut memiliki peluang untuk menentukan cara menempatkan elemen turunannya secara relatif terhadap dirinya 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 mengatasi situasi seperti tersebut secara efisien, yang dibahas di bagian pengukuran intrinsik.

Penggunaan cakupan menentukan kapan Anda dapat mengukur dan menempatkan turunan. Mengukur tata letak hanya dapat dilakukan selama pengukuran dan tata letak diteruskan, serta turunan hanya dapat ditempatkan selama tata letak diteruskan, dan hanya setelah turunan diukur. Ini diterapkan pada waktu kompilasi karena cakupan Compose seperti MeasureScope, dan PlacementScope.

Menggunakan pengubah tata letak

Anda dapat menggunakan pengubah layout untuk mengubah cara elemen diukur dan ditata letak. Layout adalah lambda; parameternya mencakup komponen yang dapat diukur, diteruskan sebagai measurable, dan batasan composable tersebut yang diteruskan, sebagai constraints. Pengubah tata letak kustom dapat terlihat seperti ini:

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

Mari kita tampilkan Text di layar dan kontrol jarak dari atas ke bagian dasar baris pertama teks. Ini persis yang dilakukan oleh pengubah paddingFromBaseline. Kami menerapkannya di sini sebagai contoh. Untuk melakukannya, gunakan pengubah layout untuk secara manual menempatkan composable di layar. Berikut adalah perilaku yang diinginkan saat padding atas Text disetel 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
) = 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.roundToPx() - 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 yang ditunjukkan oleh parameter terukur dengan memanggil measurable.measure(constraints).
  2. Anda menentukan ukuran komponen dengan memanggil metode layout(width, height), yang juga memberikan lambda yang digunakan untuk menempatkan elemen yang digabungkan. Dalam hal ini, tempatnya adalah tinggi antara bagian dasar pengukuran terakhir dan padding atas yang ditambahkan.
  3. Anda dapat memosisikan elemen yang digabungkan di layar dengan memanggil placeable.place(x, y). Jika elemen yang digabungkan tidak ditempatkan, elemen tersebut tidak akan terlihat. Posisi y sesuai dengan padding atas - yaitu posisi bagian dasar pengukuran pertama teks.

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 composable pemanggilan. Untuk mengukur dan menata letak beberapa composable, gunakan composable Layout. Composable ini memungkinkan Anda mengukur dan menata letak turunan secara manual. Semua tata letak dengan tingkat lebih tinggi seperti Column dan Row dibuat dengan composable Layout.

Mari membuat versi Column yang sangat dasar. Sebagian besar tata letak kustom mengikuti pola ini:

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

Sama dengan pengubah layout, measurables adalah daftar turunan yang perlu diukur dan constraints adalah pembatas dari induk. Dengan logika yang sama seperti sebelumnya, MyBasicColumn dapat diterapkan seperti ini:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { 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
            }
        }
    }
}

Composable turunan dibatasi oleh batasan Layout (tanpa batasan minHeight), dan penempatan tersebut didasarkan pada yPosition dari composable sebelumnya.

Berikut ini cara penggunaan komponen kustom tersebut:

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

Beberapa elemen teks menumpuk satu elemen di atas elemen berikutnya dalam kolom.

Cara kerja tata letak kustom

Pelajari tata letak dan pengubah kustom lebih lanjut di codelab Tata letak di Jetpack Compose, dan lihat cara kerja API di Contoh Compose yang membuat tata letak kustom.

Arah tata letak

Ubah arah tata letak composable dengan mengubah lokal komposisi LocalLayoutDirection.

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

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

Pengukuran intrinsik

Salah satu aturan Compose adalah Anda seharusnya hanya mengukur turunan satu kali; mengukur turunan dua kali akan memunculkan pengecualian runtime. Namun, ada kalanya Anda memerlukan beberapa informasi tentang turunan Anda sebelum mengukurnya.

Intrinsik memungkinkan Anda membuat kueri turunan sebelum benar-benar diukur.

Ke composable, Anda dapat meminta intrinsicWidth atau intrinsicHeight:

  • (min|max)IntrinsicWidth: Dengan tinggi ini, berapa lebar minimum/maksimum yang dapat Anda gambar dengan benar?
  • (min|max)IntrinsicHeight: Dengan lebar ini, berapa tinggi minimum/maksimum yang dapat Anda gambar dengan benar?

Misalnya, jika Anda meminta minIntrinsicHeight dari Text dengan width yang tidak terbatas, variabel ini akan menampilkan height dari Text seolah-olah teks digambar dalam satu baris.

Cara kerja intrinsik

Bayangkan kita ingin membuat composable yang menampilkan dua teks di layar yang dipisahkan oleh pemisah seperti ini:

Dua elemen teks berdampingan, dengan pembatas vertikal di antara elemen tersebut

Bagaimana cara melakukannya? Kita dapat memiliki Row dengan dua Text di dalamnya yang meluas sebanyak mungkin dan Divider di tengah. Kita ingin Pemisahnya setinggi Text tertinggi dan tipis (width = 1.dp).

@Composable
fun TwoTexts(
    text1: String,
    text2: String,
    modifier: Modifier = Modifier
) {
    Row(modifier = modifier) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )

        Divider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),
            text = text2
        )
    }
}

@Preview
@Composable
fun TwoTextsPreview() {
    MaterialTheme {
        Surface {
            TwoTexts(text1 = "Hi", text2 = "there")
        }
    }
}

Jika kita melihat pratinjau ini, kita melihat bahwa Pemisah meluas ke seluruh layar dan bukan itu yang kita inginkan:

Dua elemen teks berdampingan, dengan pemisah di antara keduanya, tetapi pemisah membentang di bawah bagian bawah teks

Hal ini terjadi karena Row mengukur setiap turunan secara individual dan tinggi Text tidak dapat digunakan untuk membatasi Divider. Kita ingin Divider mengisi ruang yang tersedia dengan ketinggian tertentu. Untuk itu, kita dapat menggunakan pengubah height(IntrinsicSize.Min) .

height(IntrinsicSize.Min) mengukur ukuran turunannya yang dipaksa untuk setinggi instrinsik minimum mereka. Karena bersifat berulang, ini akan membuat kueri Row dan turunannya minIntrinsicHeight.

Menerapkannya ke kode kita akan berfungsi seperti yang diharapkan:

@Composable
fun TwoTexts(
    text1: String,
    text2: String,
    modifier: Modifier = Modifier
) {
    Row(modifier = modifier.height(IntrinsicSize.Min)) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )
        Divider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),
            text = text2
        )
    }
}

Dengan pratinjau:

Dua elemen teks berdampingan, dengan pembatas vertikal di antara elemen tersebut

minIntrinsicHeight composable Row akan menjadi minIntrinsicHeight maksimum dari turunannya. Divider element's minIntrinsicHeight adalah 0 karena tidak menempati ruang jika tidak ada batasan yang diberikan; Text minIntrinsicHeight akan menjadi teks yang diberikan width tertentu. Oleh karena itu, batasan height elemen Row akan menjadi minIntrinsicHeight maksimum dari Text. Divider kemudian akan memperluas height ke batasan height yang diberikan oleh Row.

Intrinsik di tata letak kustom

Saat membuat pengubah Layout atau layout kustom, pengukuran intrinsik dihitung secara otomatis berdasarkan perkiraan. Oleh karena itu, penghitungan mungkin tidak tepat untuk semua tata letak. API ini menawarkan opsi untuk mengganti perilaku default tersebut.

Untuk menentukan ukuran instrinsik Layout kustom, ganti minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, dan maxIntrinsicHeight dari antarmuka MeasurePolicy saat membuatnya.

@Composable
fun MyCustomComposable(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    return object : MeasurePolicy {
        override fun MeasureScope.measure(
            measurables: List<Measurable>,
            constraints: Constraints
        ): MeasureResult {
            // Measure and layout here
        }

        override fun IntrinsicMeasureScope.minIntrinsicWidth(
            measurables: List<IntrinsicMeasurable>,
            height: Int
        ) = {
            // Logic here
        }

        // Other intrinsics related methods have a default value,
        // you can override only the methods that you need.
    }
}

Saat membuat pengubah layout kustom, ganti metode terkait di antarmuka LayoutModifier.

fun Modifier.myCustomModifier(/* ... */) = this.then(object : LayoutModifier {

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        // Measure and layout here
    }

    override fun IntrinsicMeasureScope.minIntrinsicWidth(
        measurable: IntrinsicMeasurable,
        height: Int
    ): Int = {
        // Logic here
    }

    // Other intrinsics related methods have a default value,
    // you can override only the methods that you need.
})

Mempelajari lebih lanjut

Pelajari pengukuran intrinsik lebih lanjut di bagian Intrinsik pada codelab Tata letak di Jetpack Compose.

Mempelajari lebih lanjut

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