Compose 修飾符

修飾元可用於裝飾或增強可組合元件。修飾元可以讓您執行下列操作:

  • 變更可組合元件的大小、版面配置、行為和外觀
  • 新增資訊,例如無障礙標籤
  • 處理使用者輸入內容
  • 新增高等級互動,例如讓元素可供點擊、可捲動、可拖曳或可縮放

修飾元是標準的 Kotlin 物件。呼叫其中一個 Modifier 類別函式以建立修飾元:

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

彩色背景上有兩行文字,文字周圍有邊框間距。

將這些函式鏈結在一起即可進行撰寫:

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

現在,文字後方的背景色彩會延長設備的完整寬度。

請注意,以上程式碼將多個不同的修飾符函式搭配使用。

  • padding 會在元素周圍放置空格。
  • fillMaxWidth 提供可組合的父項寬度上限。

最佳做法是讓「所有」可組合函式接受 modifier 參數,然後將該修飾符傳遞至第一個輸出 UI 的子項。 如此一來,程式碼就更容易重複使用,且行為更易於預測,更直覺易懂。詳情請參閱 Compose API 指南:元素接受並修改修飾元參數

修飾符的順序很重要

修飾元函式的順序 很重要。由於每個函式都會對前一個函式傳回的 Modifier 進行變更,因此序列會影響最終結果。範例如下:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

整個區域(包括邊緣周圍的邊框間距)都會回應點擊

在以上程式碼中,整個區域都可點擊,包括周圍的邊框間距,這是因為在 clickable 修飾符「之後」才套用 padding 修飾符。如果修飾元順序相反,padding 新增的空格不會回應使用者輸入的內容:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

版面配置邊緣的邊框間距不會再回應點擊

內建修飾元

Jetpack Compose 提供內建修飾元清單,可協助您裝飾或增強可組合元件。以下列舉一些常見的修飾元,可用來調整您的版面配置。

paddingsize

根據預設,Compose 中的版面配置會包裝它們的子項。不過,您可以使用 size 修飾符設定大小:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

請注意,如果指定的版面配置大小不符合版面配置的父項限制,系統可能不會採用指定的大小。如果無論輸入限制為何,您都必須修正可組合元件的大小,那麼請使用 requiredSize 修飾元:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

子圖片大於其父項限制

在這個範例中,即使父項 height 設為 100.dpImage 的高度也會是 150.dp,因為 requiredSize 修飾元有優先優勢。

便可覆寫這一置中行為。

如果您希望子項版面配置填滿父項允許的所有可用高度,請新增 fillMaxHeight 修飾元(Compose 也提供 fillMaxSizefillMaxWidth):

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

圖片高度和父項相同

如要在元素周圍加上邊框間距,請設定 padding 修飾符。

如果您想在文字基準線上方加上邊框間距,以便達成 使用從版面配置頂端到基準的特定距離 paddingFromBaseline 修飾符:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

上方有邊框間距的文字

偏移

如要根據版面配置的原始位置調整版面配置,請在前面加入 offset 修飾符,並設定 xY 軸的偏移值。 偏移值可以是正數,也可以是非正數。差異 paddingoffset 是將 offset 新增至可組合項時,並不會 變更測量值:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

文字已移至其父容器的右側

系統會按照版面配置方向水平套用 offset 修飾元。 在 從左到右 的情境中,正的 offset 會將元素向右移動,而在 向右到左 的內容中,它將元素向左移動。 如果您需要設定位移值,而不考慮版面配置方向,請參閱 absoluteOffset 修飾元,該修飾元中的正位移值一律會將元素向右移動。

offset 修飾符會提供兩個超載:offset 會將偏移值用做為參數,而 offset 則會擷取 lambda。如要進一步瞭解使用上述各項功能的時機及最佳化方式 請參閱 Compose 效能 - 盡可能延遲讀取時間一節。

Compose 中的範圍安全性

在 Compose 中,一些修飾符只有在套用於某些可組合元件的子項時才可使用。為此,Compose 會透過自訂範圍強制執行。

舉例來說,如果您想讓子項和 Box 父項一樣大,但不要使用 影響 Box 的大小,請使用 matchParentSize 修飾符matchParentSize 僅適用於 BoxScope。因此,這只能用於 Box 父項中的子項。

範圍安全性可避免新增在其他平台不支援的修飾符 可組合函式和範圍,節省反覆嘗試和出錯的時間。

限定範圍的修飾元可用來通知父項,以便其瞭解一些關於子項的資訊。這通常被稱為父項資料修飾元。這些修飾符的內部結構與一般用途的修飾符不同,但從使用的角度來看,這些差異並不重要。

BoxmatchParentSize

如上所述,如果要讓子項版面配置與父項相同大小 Box,在不影響 Box 大小的情況下,請使用 matchParentSize 修飾符。

請注意,matchParentSize 僅適用於 Box 範圍,因此僅適用於 Box 可組合元件的 直接子項。

在以下範例中,子 Spacer 從父 Box 取得其大小,而後者的大小是從最大的子 ArtistCard 中擷取的。

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

灰色背景填滿容器

如果使用 fillMaxSize 而不是 matchParentSizeSpacer 會取得父項允許的所有可用空間,進而導致父項能夠展開及填滿所有可用的空間。

灰色背景填滿螢幕

RowColumnweight

如前文 邊框間距和大小 一節的說明,根據預設,可組合元件的大小是由其包裝的內容定義的。您可以使用僅由 RowScopeColumnScope 提供的 weight 修飾元,靈活地在父項範圍內設定可組合元件的大小。

讓我們來看看包含兩個 Box 可組合元件的 Row。 第一個方塊是第二個 weight 的兩倍,因此在 寬度。由於 Row 的寬度為 210.dp,因此第一個 Box 寬為 140.dp,第二個為 70.dp

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

圖片寬度為文字寬度的兩倍

擷取及重複使用修飾符

您可以將多個修飾符鏈結在一起來裝飾或 增強可組合函式此鏈結是透過 Modifier 介面建立 代表已排序且不可變動的單一 Modifier.Elements 清單。

每個 Modifier.Element 都代表個別行為,例如版面配置、繪圖 所有手勢相關行為、焦點和語意行為 以及裝置輸入事件這些元素的排序相當重要:系統會按加入的先後次序套用修飾符元素。

有時候,在 多個可組合函式,方法是將這類可組合項擷取至變數,再提升至 範圍提升程式碼的可讀性,或改善應用程式的 成效有幾個:

  • 重組程序發生時,系統不會重複修飾符的重新分配程序 以使用
  • 修飾符的鏈結或會十分長和複雜,因此重複使用相同的鏈結執行個體,可減輕 Compose 執行階段在比較這些執行個體時的工作負載
  • 這項擷取操作可提升程式碼的簡潔性、一致性和可維護性 程式碼集

重複使用修飾符的最佳做法

建立並擷取自己的 Modifier 鏈結,以便在多個項目中重複使用 可組合元件您可以直接儲存修飾符 是類似資料的物件

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

觀察經常變更的狀態時,擷取並重複使用修飾符

觀察可組合項中經常變動的狀態 (例如動畫狀態或 scrollState) 時,系統或會執行大量重組程序。在此情況下,系統會在每次重組時分配修飾符 每次影格都會出現以下情形:

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

您可改為建立、擷取並重複使用相同的修飾符執行個體 並將其傳遞至可組合函式,如下所示:

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

擷取及重複使用未限定範圍的修飾符

修飾符可不限定範圍,或限定至特定 可組合函式。針對未限定範圍的修飾符,您可以輕鬆地擷取這類修飾符 做為簡易變數的任何可組合項:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

這個做法尤其適合與 Lazy 版面配置搭配使用。大多數 建議您將所有可能數量龐大的項目 完全相同的修飾符:

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

擷取及重複使用限定範圍修飾符

處理範圍限定於特定可組合項的修飾符時,您可以將其擷取至最高層級,並在合適的情況下重複使用:

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

您應該只將已擷取的限定範圍修飾符傳遞至相同範圍的直接子項。請參閱「範圍安全性: Compose,進一步瞭解這個做法的重要性:

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

進一步鏈結已擷取的修飾符

您可以呼叫 .then() 函式,進一步鏈結或附加已擷取的修飾符鏈結:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

請務必留意,修飾符的順序很重要!

瞭解詳情

我們提供修飾元的完整清單,以及其中的參數和範圍。

如要進一步瞭解如何使用修飾符,您也可以參考 Compose 程式碼研究室的基本版面配置,或參閱 Now in Android 存放區

如要進一步瞭解自訂修飾符及其建立方式,請參閱 請參閱「自訂版面配置 - 使用版面配置修飾符」說明文件。