Compose 版面配置中的內建函式測量資料

Compose 的其中一項規則是只能測量子項一次;如果測量子項兩次,系統將擲回執行階段例外狀況。不過有時候,您必須先掌握子項的某些相關資訊,才能進行測量。

內建函式可讓您在實際測量前查詢子項相關資訊。

如果是可組合項,您可以查詢其 intrinsicWidthintrinsicHeight

  • (min|max)IntrinsicWidth:依據這個高度,您可以正確繪製內容的最小/最大寬度為何?
  • (min|max)IntrinsicHeight:依據這個寬度,您可以正確繪製內容的最小/最大高度為何?

舉例來說,如果在 width 設為無限的情況下查詢 TextminIntrinsicHeight,系統會將文字視為繪製在單一直線上並傳回 Textheight

內建函式實際使用狀況

假設我們要建立一個可組合項,在畫面上顯示兩個文字元素並以分隔線隔開,如下所示:

兩個文字元素並排顯示,中間有一個垂直的分隔線

我們該怎麼做?我們可以設定一個 Row,在當中加入兩個 Text 並任由其擴展,然後在中間加入一個 Divider。我們將分隔線的高度設成 Text 的最大高度,寬度則為細 (width = 1.dp)。

@Composable
fun TwoTexts(
    text1: String,
    text2: String,
    modifier: Modifier = Modifier
) {
    Row(modifier = modifier) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )

        Divider(
            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")
        }
    }
}

在預覽畫面中,我們發現分隔線擴展至整個畫面,並不符合我們的預期:

兩個文字元素並排顯示,中間有一個垂直的分隔線,一直延伸至文字底部以下

之所以會發生這種情況,原因是 Row 會個別測量每個子項,而 Text 的高度無法用於限制 Divider。我們想讓 Divider 填滿指定高度的可用空間。想達到這個目的,我們可以使用 height(IntrinsicSize.Min) 修飾符。

height(IntrinsicSize.Min) 會將子項的高度強制調整為最低內建函式高度。由於該修飾符有遞迴性,因此會查詢 Row 和其子項的 minIntrinsicHeight

套用到我們的程式碼後,就能產生我們預期的結果:

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

預覽如下:

兩個文字元素並排顯示,中間有一個垂直的分隔線

Row 可組合項的 minIntrinsicHeight 將是其子項的 minIntrinsicHeight 上限。由於 Divider 元素在沒有指定限制的情況下不佔任何空間,因此其 minIntrinsicHeight 為 0;Text minIntrinsicHeight 將是該文字在指定 width 時的高度。因此,Row 元素的 height 限制將是 TextminIntrinsicHeight 上限。Divider 隨即會將其 height 擴展至 Row 指定的 height 限制。

自訂版面配置中的內建函式

建立自訂 Layoutlayout 修飾符時,內建函式測量資料是根據估計值自動計算。因此,這些計算結果的正確性會依版面配置而異。這些 API 會提供覆寫這些預設值的選項。

如要指定自訂 Layout 的內建函式測量資料,請在建立 MeasurePolicy 時覆寫 minIntrinsicWidthminIntrinsicHeightmaxIntrinsicWidthmaxIntrinsicHeight

@Composable
fun MyCustomComposable(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    return 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
        ) = {
            // Logic here
        }

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

建立自訂 layout 修飾符時,請在 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.
})

瞭解詳情

想進一步瞭解內建函式測量資料,請參閱 Jetpack Compose 程式碼研究室中版面配置的「內建函式」部分