Pengukuran intrinsik di tata letak Compose

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 IntrinsicSize.Min atau IntrinsicSize.Max:

  • Modifier.width(IntrinsicSize.Min) - Berapa lebar minimum yang Anda perlukan untuk menampilkan konten dengan benar?
  • Modifier.width(IntrinsicSize.Max) - Berapa lebar maksimum yang Anda perlukan untuk menampilkan konten dengan benar?
  • Modifier.height(IntrinsicSize.Min) - Berapa tinggi minimum yang Anda perlukan untuk menampilkan konten dengan benar?
  • Modifier.height(IntrinsicSize.Max) - Berapa tinggi maksimum yang Anda perlukan untuk menampilkan konten dengan benar?

Misalnya, jika Anda meminta minIntrinsicHeight dari Text dengan batasan width yang tidak terbatas dalam tata letak kustom, variabel ini akan menampilkan height dari Text dengan teks yang 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 Divider setinggi Text tertinggi dan tipis (width = 1.dp).

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

            text = text2
        )
    }
}

Jika melihat pratinjau ini, kita melihat bahwa Divider 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(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier.height(IntrinsicSize.Min)) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )
        VerticalDivider(
            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")
        }
    }
}

Dengan pratinjau:

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

minIntrinsicHeight Row composable akan menjadi minIntrinsicHeight maksimum dari turunannya. Divider minIntrinsicHeight elemen adalah 0 karena tidak menempati ruang jika tidak ada batasan yang diberikan; Text minIntrinsicHeight akan menjadi teks yang ditentukan width tertentu. Oleh karena itu, batasan height Row elemen akan menjadi minIntrinsicHeight maksimum dari Text. Divider kemudian akan memperluas height-nya ke batasan height yang ditentukan 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 pengukuran intrinsik Layout kustom Anda, ganti minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, dan maxIntrinsicHeight dari antarmuka MeasurePolicy saat membuatnya.

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