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 會取得父項允許的所有可用空間,進而導致父項能夠展開及填滿所有可用的空間。

灰色背景填滿螢幕

RowColumn 中的 weight

如前文 邊框間距和大小 一節的說明,根據預設,可組合元件的大小是由其包裝的內容定義的。您可以使用僅由 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 存放區

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