ConstraintLayout di Compose

ConstraintLayout adalah tata letak yang memungkinkan Anda menempatkan composable relatif terhadap composable lain di layar. Ini adalah alternatif dari penggunaan beberapa elemen Row, Column, dan Box bertingkat, serta elemen tata letak kustom bertingkat lainnya. ConstraintLayout berguna saat menerapkan tata letak yang lebih besar dengan persyaratan perataan yang lebih rumit.

Sebaiknya gunakan ConstraintLayout dalam skenario berikut:

  • Untuk menghindari penyusunan bertingkat beberapa Column dan Row untuk penempatan elemen di layar guna meningkatkan keterbacaan kode.
  • Untuk menempatkan composable relatif terhadap composable lain atau untuk menempatkan composable berdasarkan panduan, batasan, atau rantai.

Dalam sistem View, ConstraintLayout adalah cara yang disarankan untuk membuat tata letak yang besar dan kompleks, karena hierarki tampilan yang datar lebih baik untuk performa daripada tampilan bertingkat. Namun, hal ini bukan suatu masalah di Compose, yang dapat menangani hierarki tata letak dalam secara efisien.

Persiapan untuk ConstraintLayout

Untuk menggunakan ConstraintLayout di Compose, Anda perlu menambahkan dependensi ini di build.gradle (selain penyiapan Compose):

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

ConstraintLayout di Compose berfungsi dengan cara berikut menggunakan DSL:

  • Membuat referensi untuk setiap composable di ConstraintLayout menggunakan createRefs() atau createRefFor()
  • 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 composable ConstraintLayout itu sendiri.

Berikut ini contoh composable 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

API terpisah

Pada contoh ConstraintLayout, 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 ke ConstraintLayout.
  2. Tetapkan referensi yang dibuat di ConstraintSet ke composable 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.

Konsep ConstraintLayout

ConstraintLayout berisi konsep seperti panduan, batasan, dan rantai yang dapat membantu menempatkan elemen di dalam Composable.

Panduan

Panduan adalah bantuan visual kecil untuk mendesain tata letak. Composable dapat dibatasi oleh panduan. Panduan berguna untuk menempatkan elemen di dp atau percentage tertentu di dalam composable induk.

Ada dua jenis panduan yang berbeda, yaitu vertikal dan horizontal. Dua panduan horizontal adalah top dan bottom, dan dua panduan vertikal adalah start dan end.

ConstraintLayout {
    // Create guideline from the start of the parent at 10% the width of the Composable
    val startGuideline = createGuidelineFromStart(0.1f)
    // Create guideline from the end of the parent at 10% the width of the Composable
    val endGuideline = createGuidelineFromEnd(0.1f)
    //  Create guideline from 16 dp from the top of the parent
    val topGuideline = createGuidelineFromTop(16.dp)
    //  Create guideline from 16 dp from the bottom of the parent
    val bottomGuideline = createGuidelineFromBottom(16.dp)
}

Untuk membuat panduan, gunakan createGuidelineFrom* dengan jenis panduan yang diperlukan. Tindakan ini akan membuat referensi yang dapat digunakan dalam blok Modifier.constrainAs().

Batasan

Batasan mereferensikan beberapa composable untuk membuat panduan virtual berdasarkan widget yang paling ekstrem di sisi yang ditentukan.

Untuk membuat batasan, gunakan createTopBarrier() (atau: createBottomBarrier(), createEndBarrier(), createStartBarrier()), dan berikan referensi yang akan membuat batasan.

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val topBarrier = createTopBarrier(button, text)
    }
}

Batasan ini kemudian dapat digunakan dalam blok Modifier.constrainAs().

Rantai

Rantai menyediakan perilaku seperti grup dalam satu sumbu (secara horizontal atau vertikal). Sumbu lain dapat dibatasi secara tersendiri.

Untuk membuat rantai, gunakan createVerticalChain atau createHorizontalChain:

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val verticalChain = createVerticalChain(button, text, chainStyle = ChainStyle.Spread)
        val horizontalChain = createHorizontalChain(button, text)
    }
}

Rantai tersebut kemudian dapat digunakan dalam blok Modifier.constrainAs().

Rantai dapat dikonfigurasi dengan ChainStyles berbeda, yang menentukan cara menangani ruang di sekitar composable, seperti:

  • ChainStyle.Spread: Ruang didistribusikan secara merata di seluruh composable, termasuk ruang kosong sebelum composable pertama dan setelah composable terakhir.
  • ChainStyle.SpreadInside: Ruang didistribusikan secara merata di seluruh composable, tanpa ruang kosong sebelum composable pertama atau setelah composable terakhir.
  • ChainStyle.Packed: Ruang didistribusikan sebelum composable pertama dan setelah composable terakhir, composable dikemas bersama tanpa spasi di antara satu sama lain.

Pelajari lebih lanjut

Pelajari ConstraintLayout di Compose lebih lanjut dari cara kerja API dalam contoh Compose yang menggunakan ConstraintLayout.