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 butuhkan untuk menampilkan konten dengan benar?
  • Modifier.height(IntrinsicSize.Min) - Berapa tinggi minimum yang Anda butuhkan 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

Anda dapat membuat composable yang menampilkan dua teks di layar yang dipisahkan oleh pemisah:

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

Untuk melakukannya, gunakan Row dengan dua composable Text yang mengisi ruang yang tersedia, dan Divider di tengah. Divider harus setinggi Text tertinggi, dan harus 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
        )
    }
}

Divider meluas ke seluruh layar, yang bukan merupakan perilaku yang diinginkan:

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.

Agar Divider mengisi ruang yang tersedia dengan ketinggian tertentu, gunakan pengubah height(IntrinsicSize.Min).

height(IntrinsicSize.Min) mengukur ukuran turunannya agar setinggi tinggi intrinsik minimumnya. Karena pengubah ini bersifat rekursif, pengubah ini membuat kueri minIntrinsicHeight dari Row dan turunannya.

Menerapkan pengubah ini ke kode Anda akan membuatnya 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

Tinggi Row ditentukan sebagai berikut:

  • minIntrinsicHeight composable Row adalah minIntrinsicHeight maksimum dari turunannya.
  • minIntrinsicHeight elemen Divider adalah 0, karena tidak menempati ruang jika tidak ada batasan yang diberikan.
  • Text minIntrinsicHeight adalah teks untuk width tertentu.
  • Oleh karena itu, batasan height Row elemen akan menjadi minIntrinsicHeight maksimum dari Text.
  • Divider kemudian memperluas height-nya 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 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.
}