Compose 中的文字

文字是任何 UI 的核心,Jetpack Compose 可幫助讓顯示或撰寫文字變更簡單。Compose 可以善用其建構塊的組合,這意味著您不需要覆寫屬性和方法,也不需要擴充大型類別,即可擁有特定可組合項設計以及按照您想要的方式執行的邏輯。

Compose 作為其基礎,提供了屬於準系統的 BasicTextBasicTextField 用來顯示文字和處理使用者輸入內容。在更高級別,Compose 提供 TextTextField,這是根據質感設計準則所構成的可組合元件。我們建議您使用它們,因為這樣能夠使得 Android 使用者擁有良好的外觀和風格,同時提供其他選項來簡化自訂程序,而且完全不必編寫大量程式碼。

顯示文字

顯示文字最基本的方法,就是使用 Text 可組合元件搭配 String 作為引數:

@Composable
fun SimpleText() {
  Text("Hello World")
}

以黑色純文字顯示的「Hello World」字詞

顯示資源中的文字

我們建議您使用字串資源,而非針對 Text 值進行硬式編碼,因為這樣您可以和 Android 檢視畫面共用相同的字串,以及做好應用程式國際化準備工作:

@Composable
fun StringResourceText() {
  Text(stringResource(R.string.hello_world))
}

樣式文字

Text 可組合元件提供多個選用參數,可用來設定其內容樣式。以下我們列出涵蓋最常見用途以及文字的參數。如要查看 Text 的所有參數,我們建議您查看 Compose 文字原始碼

每當您設定其中一個參數,就會將樣式套用到整個文字值。如果您需要在同一行或同一段落中套用多種樣式,請參閱多種內嵌樣式一節。

變更文字顏色

@Composable
fun BlueText() {
  Text("Hello World", color = Color.Blue)
}

以藍色文字顯示的「Hello World」字詞

變更文字大小

@Composable
fun BigText() {
  Text("Hello World", fontSize = 30.sp)
}

字型尺寸較大的「Hello World」字詞

將文字設定為斜體

使用 fontStyle 參數將文字設為斜體 (或設定另一個 FontStyle)。

@Composable
fun ItalicText() {
  Text("Hello World", fontStyle = FontStyle.Italic)
}

斜體字型的「Hello World」字詞

將文字設定為粗體

使用 fontWeight 參數將文字設為粗體 (或設定其他 FontWeight)。

@Composable
fun BoldText() {
    Text("Hello World", fontWeight = FontWeight.Bold)
}

粗體字型的「Hello World」字詞

文字對齊

textAlign 參數可讓您設定 Text 可組合表面區域中的文字對齊

根據預設,Text 會根據內容值選擇自然文字對齊:

  • Text 容器的左邊緣為由左至右的字母,例如拉丁文、斯拉夫文或韓文
  • Text 容器的右邊緣為由右至左的字母,例如阿拉伯文或希伯來文
@Preview(showBackground = true)
@Composable
fun CenterText() {
    Text("Hello World", textAlign = TextAlign.Center,
                modifier = Modifier.width(150.dp))
}

「Hello World」一詞在其所包含的元素內置中

如要手動設定 Text 可組合元件的文字對齊方式,最好個別使用 TextAlign.StartTextAlign.End,而不要使用 TextAlign.LeftTextAlign.Right,因為它們會根據偏好語言文字方向解析 Text 可組合元件的右邊緣。舉例來說, TextAlign.End 會對齊法文的右側以及阿拉伯文的左側,但無論使用何種字母,TextAlign.Right 都會對齊右側。

使用字型

TextfontFamily 參數可供設定在可組合元件中使用的字型。根據預設,Serif、Sal-Sert、等寬和草寫字型系列已包含

@Composable
fun DifferentFonts() {
    Column {
        Text("Hello World", fontFamily = FontFamily.Serif)
        Text("Hello World", fontFamily = FontFamily.SansSerif)
    }
}

兩個不同字型的「Hello World」字詞 (無論是否包含 Serif)

可以使用 fontFamily 屬性搭配 res/fonts 資料夾中定義的自訂字型和字體:

開發環境中 res > 字型資料夾的圖形描述

以下範例說明如何根據這些字型檔案以及使用 Font 函式定義 fontFamily

val firaSansFamily = FontFamily(
        Font(R.font.firasans_light, FontWeight.Light),
        Font(R.font.firasans_regular, FontWeight.Normal),
        Font(R.font.firasans_italic, FontWeight.Normal, FontStyle.Italic),
        Font(R.font.firasans_medium, FontWeight.Medium),
        Font(R.font.firasans_bold, FontWeight.Bold)
)

最後,您可以將這個 fontFamily 傳遞給 Text 的可組合元件。由於 fontFamily 可包含不同的粗細,因此您可以手動設定 fontWeight 來為文字選取適當的粗細:

Column {
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Light)
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal)
    Text(
        ..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal,
        fontStyle = FontStyle.Italic
    )
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Medium)
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Bold)
}

幾種不同字型粗細和樣式的「Hello World」字詞。

若要瞭解如何在整個應用程式中設定印刷樣式,請參閱主題說明文件

文字中的多種樣式

如要在相同的 Text 可組合元件中設定不同樣式,您必須使用 AnnotatedString,它是可使用任意註解樣式加上註解的字串。

AnnotatedString 是包含以下的資料類別:

  • Text
  • SpanStyleRangeList,等同於文字值內位置範圍的內嵌樣式
  • ParagraphStyleRangeList,用於指定文字對齊方式、文字方向、行高和文字縮排樣式

TextStyle 用於 Text 可組合元件中,其中 SpanStyleParagraphStyle 適用於 AnnotatedString

SpanStyleParagraphStyle 的差異在於 ParagraphStyle 可以套用至整個段落,SpanStyle 則可套用字元層級。如果部分文字有加上 ParagraphStyle 標記,則該部分會與其餘部分分開,如同在開頭和結尾部分加上換行字元。

AnnotatedString 具有型別安全建構工具,可讓您輕鬆建立:buildAnnotatedString

@Composable
fun MultipleStylesInText() {
    Text(
        buildAnnotatedString {
            withStyle(style = SpanStyle(color = Color.Blue)) {
                append("H")
            }
            append("ello ")

            withStyle(style = SpanStyle(fontWeight = FontWeight.Bold, color = Color.Red)) {
                append("W")
            }
            append("orld")
        }
    )
}

顯示「Hello World」這個擁有數個樣式變更內嵌的字詞;H 是藍色,W 是紅色和粗體

我們能夠以相同方式設定 ParagraphStyle

@Composable
fun ParagraphStyle() {
    Text(
        buildAnnotatedString {
            withStyle(style = ParagraphStyle(lineHeight = 30.sp)) {
                withStyle(style = SpanStyle(color = Color.Blue)) {
                    append("Hello\n")
                }
                withStyle(
                    style = SpanStyle(
                        fontWeight = FontWeight.Bold,
                        color = Color.Red
                    )
                ) {
                    append("World\n")
                }
                append("Compose")
            }
        }
    )
}

三個段落分成三種樣式:藍色、紅色、粗體和純黑色

行數上限

如要限制 Text 可組合元件的可見行數,請設定 maxLines 參數:

@Composable
fun LongText() {
    Text("hello ".repeat(50), maxLines = 2)
}

長文字段落在兩行之後截斷

文字溢位

限制長文字時,您可能需要指定 TextOverflow;它只有在顯示的文字遭到截斷時才會顯示。指定方法是設定 textOverflow 參數:

@Composable
fun OverflowedText() {
    Text("Hello Compose ".repeat(50), maxLines = 2, overflow = TextOverflow.Ellipsis)
}

文字的長段落在三行後截斷,結尾是省略符號

主題設定

如要使用應用程式主題設定文字樣式,請參閱主題說明文件

使用者互動

Jetpack Compose 可在 Text 中進行精細互動。文字選取功能現在更具彈性,並且可以在可組合項的版面配置中完成。文字中的使用者互動與其他可組合項的版面配置不同,因為您無法在一部分 Text 可組合項中新增修飾詞。本節重點介紹各種可用於啟用使用者互動的 API。

選取文字

根據預設,可組合項是無法選取的,也就是說,在預設情況下,使用者無法選取及複製應用程式中的文字。如要啟用文字選取功能,必須使用 SelectionContainer 可組合項包裝文字元素:

@Composable
fun SelectableText() {
    SelectionContainer {
        Text("This text is selectable")
    }
}

由使用者選取的簡短文字段落。

您可能需要針對可選取區域的特定部分停用選取功能。如要這麼做,您必須使用 DisableSelection 可組合元件納入無法選取的部分:

@Composable
fun PartiallySelectableText() {
    SelectionContainer {
        Column {
            Text("This text is selectable")
            Text("This one too")
            Text("This one as well")
            DisableSelection {
                Text("But not this one")
                Text("Neither this one")
            }
            Text("But again, you can select this one")
            Text("And this one too")
        }
    }
}

較長的文字段落。使用者嘗試選取整個段落,但因為兩行已經套用 DisableSelection,因此並未選取該選項。

取得點擊文字的位置

如要監聽 Text 上的點擊,您可以新增 clickable 輔助鍵。但是,如果您想在 Text 可組合項中取得點選位置,在對文字的不同部分執行不同動作的情況下,就必須改用 ClickableText

@Composable
fun SimpleClickableText() {
    ClickableText(
        text = AnnotatedString("Click Me"),
        onClick = { offset ->
            Log.d("ClickableText", "$offset -th character is clicked.")
        }
    )
}

附帶備註的點擊

當使用者按一下 Text 可組合元件時,您可能需要在部分 Text 值中附加其他資訊,例如附加至特定字詞以便在瀏覽器中開啟的網址:為此,您需要附加註解,將標記 (String)、項目 (String) 和文字範圍做為參數。使用 AnnotatedString 即可以標記或文字範圍來篩選這些註解。範例如下:

@Composable
fun AnnotatedClickableText() {
    val annotatedText = buildAnnotatedString {
        append("Click ")

        // We attach this *URL* annotation to the following content
        // until `pop()` is called
        pushStringAnnotation(tag = "URL",
                             annotation = "https://developer.android.com")
        withStyle(style = SpanStyle(color = Color.Blue,
                                    fontWeight = FontWeight.Bold)) {
            append("here")
        }

        pop()
    }

    ClickableText(
        text = annotatedText,
        onClick = { offset ->
            // We check if there is an *URL* annotation attached to the text
            // at the clicked position
            annotatedText.getStringAnnotations(tag = "URL", start = offset,
                                                    end = offset)
                .firstOrNull()?.let { annotation ->
                    // If yes, we log its value
                    Log.d("Clicked URL", annotation.item)
                }
        }
    )
}

輸入及修改文字

TextField 可讓使用者輸入及修改文字。TextField 實作分為兩個層級:

  1. TextField 是質感設計實作。我們建議您選擇這項實作程序,因為它有遵守質感設計準則
    • 填入預設樣式
    • OutlinedTextField外框樣式版本
  2. BasicTextField 可讓使用者透過硬體或螢幕鍵盤編輯文字,但無法提供例如提示或預留位置等裝飾。
@Composable
fun SimpleFilledTextFieldSample() {
    var text by remember { mutableStateOf("Hello") }

    TextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )
}

含有「Hello」字詞的可編輯文字欄位。這個欄位含有不可編輯的標籤「label」。

@Composable
fun SimpleOutlinedTextFieldSample() {
    var text by remember { mutableStateOf("") }

    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )
}

可編輯文字欄位,帶有紫色邊框和標籤。

設定文字欄位樣式

TextFieldBasicTextField 會共用許多常見的參數以進行自訂。TextField 原始碼內提供 TextField 的完整清單。這份清單僅列舉部分有用參數的內容:

  • singleLine
  • maxLines
  • textStyle
@Composable
fun StyledTextField() {
    var value by remember { mutableStateOf("Hello\nWorld\nInvisible") }

    TextField(
        value = value,
        onValueChange = { value = it },
        label = { Text("Enter text") },
        maxLines = 2,
        textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold),
        modifier = Modifier.padding(20.dp)
    )
}

多行文字欄位,包含可編輯的兩行加上標籤

如果設計需要 Material TextField 或 OutlineTextField,我們建議使用 BasicTextField 而非 TextField。然而,如果建構的設計不需要使用材質規格的裝飾,則應使用 BasicTextField

鍵盤選項

TextField 可讓您設定鍵盤設定選項 (例如鍵盤配置);或是啟用自動更正功能 (如果鍵盤支援)。如果螢幕鍵盤不符合此處提供的選項,可能無法保證某些選項能夠使用。以下是支援的鍵盤選項清單:

  • capitalization
  • autoCorrect
  • keyboardType
  • imeAction

格式設定

TextField 可讓您為輸入值設定 VisualTransformation,例如將密碼中的字符更換為 *,或是在信用卡號碼中每隔 4 位數字插入一個連字號:

@Composable
fun PasswordTextField() {
    var password by rememberSaveable { mutableStateOf("") }

    TextField(
        value = password,
        onValueChange = { password = it },
        label = { Text("Enter password") },
        visualTransformation = PasswordVisualTransformation(),
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
    )
}

密碼文字輸入欄位,套用遮罩文字

你可以在 VisualTransformSamples 原始碼中找到更多範例。

清理輸入內容

編輯文字的常見工作是移除前置字元,或在每次變更文字時轉換輸入字串。

作為模範,您應該假設每個 onValueChange 都可進行任意和大型編輯。舉例來說,如果使用者使用自動更正功能,並以表情符號取代字詞或其他智慧編輯功能,就可能發生這種情況。為了正確處理這種情況,請在編寫任何轉換邏輯時假設,傳遞至 onValueChange 的目前文字與將被傳遞至 onValueChange 的前一個或下一個值無關。

如要實作不允許以零開頭的文字欄位,只要在每次變更值時移除所有以零開頭的文字即可。

@Composable
fun NoLeadingZeroes() {
  var input by rememberSaveable { mutableStateOf("") }
  TextField(
      value = input,
      onValueChange = { newText ->
          input = newText.trimStart { it == '0' }
      }
  )
}

如要在清除文字時控制遊標位置,請使用 TextFieldTextFieldValue 超載做為狀態的一部分。