Pengubah Compose

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

  • Mengubah ukuran, tata letak, perilaku, dan tampilan composable
  • 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:

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

Dua baris teks pada latar belakang berwarna, dengan padding di sekeliling teks.

Anda dapat menggabungkan fungsi-fungsi berikut untuk mengomposisikannya:

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

Latar belakang berwarna di belakang teks kini diperluas menurut lebar penuh perangkat.

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

  • padding menempatkan ruang di sekitar elemen.
  • fillMaxWidth membuat composable mengisi lebar maksimum yang diberikan kepadanya dari induknya.

Praktik terbaiknya adalah semua composable Anda menerima parameter modifier, dan meneruskan pengubah tersebut ke turunan pertamanya yang menampilkan UI. Dengan melakukannya, kode Anda akan lebih mudah digunakan kembali dan membuat perilakunya lebih dapat diprediksi serta intuitif. Untuk informasi selengkapnya, lihat panduan Compose API, Elemen menerima dan mematuhi parameter Pengubah.

Pentingnya urutan pengubah

Urutan fungsi pengubah bersifat 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. Berikut adalah beberapa pengubah umum yang akan Anda gunakan untuk menyesuaikan tata letak.

padding dan size

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

Untuk menambahkan padding di sekitar elemen, setel pengubah padding.

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 memosisikan 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 menambahkan offset ke 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.

Pengubah offset menyediakan dua overload - offset yang menggunakan offset sebagai parameter dan offset yang menggunakan lambda. Untuk informasi yang lebih mendalam tentang kapan harus menggunakan setiap pengubah tersebut dan cara mengoptimalkan performa, baca bagian Performa Compose - Menunda pembacaan selama mungkin.

Keamanan cakupan di Compose

Di Compose terdapat pengubah yang hanya dapat digunakan saat diterapkan ke turunan composable tertentu. Compose menerapkannya melalui cakupan kustom.

Misalnya, jika Anda ingin membuat turunan sebesar Box induk tanpa memengaruhi ukuran Box, gunakan pengubah matchParentSize. matchParentSize hanya tersedia di BoxScope. Oleh karenanya, ini hanya dapat digunakan pada turunan dalam induk Box.

Keamanan cakupan mencegah Anda menambahkan pengubah yang tidak akan berfungsi di composable dan cakupan lain serta menghemat waktu untuk uji coba.

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 dalam 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 Box composable.

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

weight di Row dan Column

Seperti yang telah Anda lihat di bagian sebelumnya tentang Padding dan ukuran, secara default, ukuran composable ditentukan oleh konten yang digabungkannya. 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

Mengekstrak dan menggunakan kembali pengubah

Beberapa pengubah dapat dirantai untuk mendekorasi atau meningkatkan composable. Rantai ini dibuat melalui antarmuka Modifier yang mewakili satu daftar Modifier.Elements yang diurutkan dan tidak dapat diubah.

Setiap Modifier.Element mewakili satu perilaku, seperti perilaku tata letak, gambar dan grafis, semua perilaku terkait gestur, fokus, dan semantik, serta peristiwa input perangkat. Urutannya penting: elemen pengubah yang ditambahkan terlebih dahulu akan diterapkan terlebih dahulu.

Terkadang, akan bermanfaat jika menggunakan kembali instance rantai pengubah yang sama dalam beberapa composable, dengan mengekstraknya ke dalam variabel dan mengangkatnya ke dalam cakupan yang lebih tinggi. Hal ini dapat meningkatkan keterbacaan kode atau membantu meningkatkan performa aplikasi karena beberapa alasan:

  • Alokasi ulang pengubah tidak akan diulang saat rekomposisi terjadi untuk composable yang menggunakannya
  • Rantai pengubah berpotensi sangat panjang dan rumit, sehingga menggunakan kembali instance rantai yang sama dapat mengurangi beban kerja yang perlu dilakukan runtime Compose saat membandingkannya
  • Ekstraksi ini mendukung kebersihan kode, konsistensi, dan pemeliharaan kode di seluruh codebase

Praktik terbaik untuk menggunakan kembali pengubah

Buat rantai Modifier Anda sendiri dan ekstrak untuk digunakan kembali pada beberapa komponen composable. Anda dapat menyimpan pengubah karena pengubah merupakan objek yang mirip data:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

Mengekstrak dan menggunakan kembali pengubah saat mengamati status yang sering berubah

Saat mengamati status yang sering berubah di dalam composable, seperti status animasi atau scrollState, mungkin ada sejumlah besar rekomposisi yang dilakukan. Dalam hal ini, pengubah akan dialokasikan pada setiap rekomposisi dan kemungkinan besar untuk setiap frame:

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

Sebagai gantinya, Anda dapat membuat, mengekstrak, dan menggunakan kembali instance pengubah yang sama dan meneruskannya ke composable seperti ini:

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

Mengekstrak dan menggunakan kembali pengubah tidak tercakup

Pengubah dapat tidak dicakupkan atau dicakupkan ke composable tertentu. Untuk pengubah tidak tercakup, Anda dapat dengan mudah mengekstraknya di luar composable sebagai variabel sederhana:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

Hal ini dapat bermanfaat terutama jika digabungkan dengan tata letak Lambat. Dalam kebanyakan kasus, Anda ingin semua jumlah item yang berpotensi signifikan memiliki pengubah yang sama persis:

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

Mengekstrak dan menggunakan kembali pengubah tercakup

Saat menangani pengubah yang dicakupkan ke composable tertentu, Anda dapat mengekstraknya ke tingkat tertinggi dan menggunakan kembali jika sesuai:

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

Anda hanya boleh meneruskan pengubah tercakup yang diekstrak ke turunan langsung dengan cakupan yang sama. Lihat bagian Keamanan cakupan di Compose untuk mengetahui referensi selengkapnya tentang alasan pentingnya hal ini:

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

Perantaian lebih lanjut dari pengubah yang diekstrak

Anda dapat membuat rantai atau menambahkan rantai pengubah yang diekstrak lebih lanjut dengan memanggil fungsi .then():

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

Perhatikan bahwa urutan pengubah sangat penting!

Pelajari lebih lanjut

Kami menyediakan daftar lengkap pengubah, beserta parameter dan cakupannya.

Untuk mengetahui praktik lain tentang cara menggunakan pengubah, Anda juga dapat membuka codelab Tata letak dasar di Compose atau lihat Repositori Now di Android.

Untuk informasi selengkapnya tentang pengubah kustom dan cara membuatnya, lihat dokumentasi tentang Tata letak kustom - Menggunakan pengubah tata letak.