Compose düzenlerindeki içsel ölçümler

Compose'un kurallarından biri, alt öğelerinizi yalnızca bir kez ölçmeniz gerektiğidir. Alt öğeleri iki kez ölçmek, çalışma zamanı istisnası oluşturur. Ancak, çocukları ölçmeden önce onlar hakkında bazı bilgilere ihtiyaç duyduğunuz zamanlar olur.

İçsel özellikler, alt öğeleri gerçekte ölçülmeden önce sorgulamanıza olanak tanır.

Bir composable'ın IntrinsicSize.Min veya IntrinsicSize.Max değerini isteyebilirsiniz:

  • Modifier.width(IntrinsicSize.Min) - İçeriğinizi düzgün bir şekilde görüntülemek için gereken minimum genişlik nedir?
  • Modifier.width(IntrinsicSize.Max) - İçeriğinizi düzgün bir şekilde görüntülemek için gereken maksimum genişlik nedir?
  • Modifier.height(IntrinsicSize.Min) - İçeriğinizi düzgün bir şekilde görüntülemek için gereken minimum yükseklik nedir?
  • Modifier.height(IntrinsicSize.Max) - İçeriğinizi düzgün bir şekilde görüntülemek için gereken maksimum yükseklik nedir?

Örneğin, özel bir düzende sonsuz width kısıtlamaları olan bir Text öğesinin minIntrinsicHeight değerini sorarsanız metin tek bir satırda çizilmiş olarak Text öğesinin height değerini döndürür.

İçsel değerlerin kullanımı

Ekranda iki metni ayırıcıyla ayırarak gösteren bir composable oluşturabilirsiniz:

Aralarında dikey ayırıcı bulunan yan yana iki metin öğesi

Bunun için kullanılabilir alanı dolduran iki Text bileşeni ve ortada bir Divider içeren Row kullanın. Divider, en uzun Text kadar uzun ve ince (width = 1.dp) olmalıdır.

@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, tüm ekrana genişletilir. Bu, istenen davranış değildir:

Aralarında ayırıcı bulunan yan yana iki metin öğesi. Ayırıcı, metnin alt kısmının altına kadar uzanıyor.

Bunun nedeni, Row öğesinin her alt öğeyi ayrı ayrı ölçmesi ve Text yüksekliğinin Divider öğesini kısıtlamak için kullanılamamasıdır.

Divider öğesinin, kullanılabilir alanı belirli bir yükseklikle doldurması için height(IntrinsicSize.Min) değiştiricisini kullanın.

height(IntrinsicSize.Min), alt öğelerini minimum iç yükseklikleri kadar uzun olacak şekilde boyutlandırır. Bu değiştirici yinelemeli olduğundan Row ve alt öğelerinin minIntrinsicHeight değerini sorgular.

Bu değiştiriciyi kodunuza uyguladığınızda kod beklendiği gibi çalışır:

@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")
        }
    }
}

Önizlemeli:

Aralarında dikey ayırıcı bulunan yan yana iki metin öğesi

Row öğesinin yüksekliği aşağıdaki şekilde belirlenir:

  • Row composable'ının minIntrinsicHeight değeri, alt öğelerinin maksimum minIntrinsicHeight değeridir.
  • Divider öğesinin minIntrinsicHeight değeri 0'dır. Herhangi bir kısıtlama belirtilmediğinde yer kaplamaz.
  • Text minIntrinsicHeight, belirli bir width için metnin değeridir.
  • Bu nedenle, Row öğesinin height kısıtlaması, Text'lerin maksimum minIntrinsicHeight değeri olur.
  • Divider, height değerini Row tarafından verilen height kısıtlamasına göre genişletir.

Özel düzenlerinizdeki içsel öğeler

Özel bir Layout veya layout değiştirici oluştururken içsel ölçümler, yaklaşık değerlere göre otomatik olarak hesaplanır. Bu nedenle, hesaplamalar tüm düzenler için doğru olmayabilir. Bu API'ler, varsayılan ayarları geçersiz kılma seçenekleri sunar.

Özel Layout öğenizin içsel ölçümlerini belirtmek için MeasurePolicy arayüzünü oluştururken minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth ve maxIntrinsicHeight değerlerini geçersiz kılın. Geçersiz kılma işleminiz, measure tarafından yapılan işlemle eşleşmelidir. Örneğin, alt öğelerini dikey olarak yığınlayan ve her alt öğeye tam sütun genişliği veren özel bir sütunda, belirli bir width için minimum iç yükseklik, alt öğelerinin bu width için minimum iç yüksekliklerinin toplamıdır:

@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 {
                val childConstraints = constraints.copy(minHeight = 0)
                val placeables = measurables.map { measurable ->
                    measurable.measure(childConstraints)
                }
                val width = constraints.constrainWidth(
                    placeables.maxOfOrNull { it.width } ?: 0
                )
                val height = constraints.constrainHeight(
                    placeables.sumOf { it.height }
                )
                return layout(width, height) {
                    var yPosition = 0
                    placeables.forEach { placeable ->
                        placeable.placeRelative(x = 0, y = yPosition)
                        yPosition += placeable.height
                    }
                }
            }

            override fun IntrinsicMeasureScope.minIntrinsicHeight(
                measurables: List<IntrinsicMeasurable>,
                width: Int
            ): Int {
                return measurables.sumOf { measurable ->
                    measurable.minIntrinsicHeight(width)
                }
            }

            // Other intrinsics related methods have a default value,
            // you can override only the methods that you need.
        }
    )
}

Özel layout değiştiricinizi oluştururken LayoutModifier arayüzündeki ilgili yöntemleri geçersiz kılın. İçsel hesaplamanın measure ile eşleşmesi gerekir. Örneğin, belirli bir height için her tarafa sabit dolgu ekleyen bir değiştirici, alt öğenin minIntrinsicWidth değerini sorgulamadan önce dikey dolguyu çıkarır ve sonuca yatay dolguyu ekler:

fun Modifier.myCustomModifier(padding: Dp = 8.dp) =
    this then object : LayoutModifier {
        override fun MeasureScope.measure(
            measurable: Measurable,
            constraints: Constraints
        ): MeasureResult {
            val paddingPx = padding.roundToPx()
            val horizontal = paddingPx * 2
            val vertical = paddingPx * 2
            val placeable = measurable.measure(
                constraints.offset(-horizontal, -vertical)
            )
            val width = constraints.constrainWidth(placeable.width + horizontal)
            val height = constraints.constrainHeight(placeable.height + vertical)
            return layout(width, height) {
                placeable.place(paddingPx, paddingPx)
            }
        }

        override fun IntrinsicMeasureScope.minIntrinsicWidth(
            measurable: IntrinsicMeasurable,
            height: Int
        ): Int {
            val paddingPx = padding.roundToPx()
            val horizontal = paddingPx * 2
            val vertical = paddingPx * 2
            val childHeight = if (height == Constraints.Infinity) {
                Constraints.Infinity
            } else {
                (height - vertical).coerceAtLeast(0)
            }
            return measurable.minIntrinsicWidth(childHeight) + horizontal
        }

        // Other intrinsics related methods have a default value,
        // you can override only the methods that you need.
    }