自訂版面配置

在 Compose 中,UI 元素以可組合函式表示;這些函式會在叫用時發出 UI,然後新增至螢幕上算繪的 UI 樹狀結構中。每個 UI 元素都有一個父項,而且可能有多個子項。每個元素也位於父項中,指定為 (x, y) 位置,大小指定為 widthheight

父項會定義子項元素的限制。元素只能在上述限制內定義大小。限制用於界定元素的最小及最大 widthheight。如果元素具有子項元素,可能會測量每個子項來協助判斷其大小。一旦元素確定及報告其大小後,就有機會定義子元素相對於自己的位置。詳情請參閱建立自訂版面配置一文。

為 UI 樹狀結構中的每個節點進行版面配置需要完成三個步驟。每個節點必須符合下列條件:

  1. 測量任何子項
  2. 決定其自己的大小
  3. 放置子項

為節點進行版面配置的三個步驟:測量子項、確定大小、放置子項

使用範圍用於定義何時可以測量及放置子項只能在測量和版面配置期間測量版面配置,且只能在事先測量過之後,才能在版面配置期間放置子項。受 Compose 範圍限制,例如 MeasureScopePlacementScope,這要求在編譯時間強制執行。

使用版面配置輔助鍵

您可以使用 layout 輔助鍵來修改元素的測量及版面配置方式。Layout 是 lambda;其參數包含可進行測量的元素,傳遞為 measurable,以及該可組合元素的連入限制,傳遞為 constraints。自訂版面配置輔助鍵的外觀如下:

fun Modifier.customLayoutModifier(...) =
    this.layout { measurable, constraints ->
        ...
    })

我們在螢幕上顯示 Text,並控制從第一行文字的頂端到底部的距離。paddingFromBaseline 輔助鍵正是如此,我們會在此處實作範例。 方法是使用 layout 輔助鍵手動將可組合元素放置在螢幕上。以下是預期行為,即 Text 頂端邊框間距設為 24.dp

顯示標準 UI 邊框間距(設定元素之間的距離)與文字邊框間距(設定一個底線到下一個底線的距離)之間的差異。

以下是產生此間距的程式碼:

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

程式碼中的情況如下:

  1. measurablelambda 參數中,您可以呼叫 measurable.measure(constraints) 來測量 Text,它透過可測量參數表示。
  2. 透過呼叫 layout(width, height) 方法可指定可組合元素的大小,此方法也會提供用於放置已封裝元素的 lambda。在本例中,它是上一個底線及新增頂端邊框間距的高度。
  3. 呼叫 placeable.place(x, y) 可以將封裝的元素放到螢幕中。如果未放置已封裝的元素,這些元素就不會顯示。y 位置對應至頂端邊框間距 - 第一個文字底線的位置。

如要驗證此設定是否正常運作,請在 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))
    }
}

多次預覽文字元素;其中之一表示元素之間的一般邊框間距,另一個顯示一個底線到下一個底線的邊框間距

建立自訂版面配置

layout 輔助鍵只能變更呼叫的可組合元素。如要測量多個可組合元素並為其進行版面配置,請改用 Layout 可組合元素。此可組合元素可以手動測量及調整子項的版面配置。所有較高層級的版面配置(如 ColumnRow),均以 Layout 可組合元素進行建構。

讓我們先建構出基本的 Column 版本。大部分自訂版面配置都會沿用下列模式:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
    }
}

layout 輔助鍵類似,measurables 是一列需要測量的子項,而 constraints 則是父項的限制。可以按照先前採用的邏輯實作 MyBasicColumn,操作如下:

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

子項可組合元件受 Layout 限制(沒有 minHeight 限制)的限制,基於前一個可組合元件的 yPosition 放置。

自訂可組合元件的使用方式如下:

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

數個文字元素在一欄中上下堆疊。

版面配置方向

變更 LocalLayoutDirection 組合文字以變更可組合元件的版面配置方向。

如果要在螢幕上手動放置可組合元素,LayoutDirectionlayout 輔助鍵的 LayoutScopeLayout 可組合元素的一部分。

使用 layoutDirection 時,請使用 place 放置可組合元素。與 placeRelative 方法不同,place 不會根據版面配置方向(從左至右或從右至左)變更。

自訂版面配置的實際操作

如要進一步瞭解自訂版面配置及輔助鍵,請參閱 Jetpack Compose 程式碼研究室的版面配置,以及撰寫建立自訂版面配置的範例

瞭解詳情

如要進一步瞭解 Compose 的自訂版面配置,請參閱以下其他資源。

影片