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.

Menetapkan tata letak setiap node di hierarki UI merupakan proses tiga langkah. Setiap node harus:

  1. Mengukur setiap turunan
  2. Menentukan ukurannya sendiri
  3. Menempatkan turunannya

Tiga langkah tata letak node: mengukur turunan, menentukan ukuran, menempatkan turunan

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). Hal 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() =
    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 ruang antar-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 composable 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 level 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,
        content = 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.

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

Cara kerja tata letak kustom

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

Pelajari lebih lanjut

Untuk mempelajari tata letak kustom di Compose lebih lanjut, lihat referensi tambahan berikut.

Video