文字对任何界面都属于核心内容,而利用 Jetpack Compose 可以更轻松地显示或写入文字。Compose 可以充分利用其构建块的组合,这意味着您无需覆盖各种属性和方法,也无需扩展大型类,即可拥有特定的可组合项设计以及按您期望的方式运行的逻辑。
Compose 提供了基础的 BasicText
和 BasicTextField
,它们是用于显示文字以及处理用户输入的主要函数。Compose 还提供了更高级的 Text
和 TextField
,它们是遵循 Material Design 准则的可组合项。建议在 Android 平台上使用这些构建块,因为它们的外观和样式非常适合 Android 用户,而且还包括可用以简化用户自定义设置的其他选项,无需编写大量代码。
显示文字
显示文字的最基本方法是使用以 String
作为参数的 Text
可组合项:
@Composable
fun SimpleText() {
Text("Hello World")
}
显示资源中的文字
我们建议您使用字符串资源,而不是对 Text
值进行硬编码,因为使用字符串资源时您可以与 Android 视图共享相同的字符串,并为您的应用国际化做好准备:
@Composable
fun StringResourceText() {
Text(stringResource(R.string.hello_world))
}
设置文字样式
Text
可组合项有多个用于为其内容设置样式的可选参数。
以下列出了适用于最常见文字用例的参数。如需查看 Text
的所有参数,建议您查阅 Compose Text 源代码。
每当您设置其中任何一个参数,都会将样式应用于整个文字值。如果您需要在同一行或段落中应用多种样式,请参阅有关多种内嵌样式的部分。
更改文字颜色
@Composable
fun BlueText() {
Text("Hello World", color = Color.Blue)
}
更改字号
@Composable
fun BigText() {
Text("Hello World", fontSize = 30.sp)
}
将文字设为斜体
使用 fontStyle
参数可以将文字设为斜体(或设置其他 FontStyle
)。
@Composable
fun ItalicText() {
Text("Hello World", fontStyle = FontStyle.Italic)
}
将文字设为粗体
使用 fontWeight
参数将文字设为粗体(或设置其他 FontWeight
)。
@Composable
fun BoldText() {
Text("Hello World", fontWeight = FontWeight.Bold)
}
文字对齐
通过 textAlign
参数,您可以在 Text
可组合项的 Surface 区域内设置文字的对齐方式。
默认情况下,Text
会根据其内容值选择自然的文字对齐方式:
- 对于从左到右书写的文字,如拉丁语、西里尔文或朝鲜文,向
Text
容器的左边缘对齐 - 对于从右到左书写的文字,如阿拉伯语或希伯来语,向
Text
容器的右边缘对齐
@Preview(showBackground = true)
@Composable
fun CenterText() {
Text("Hello World", textAlign = TextAlign.Center,
modifier = Modifier.width(150.dp))
}
如果您想手动设置 Text
可组合项的文字对齐方式,最好分别使用 TextAlign.Start
和 TextAlign.End
(而不要使用 TextAlign.Left
和 TextAlign.Right
),这样系统就可以根据具体语言的首选文字方向,将您的设置解析为向 Text
的正确边缘对齐。例如,TextAlign.End
对于法语文字将向右侧对齐,而对于阿拉伯语文字则将向左侧对齐,但无论对于哪种文字,TextAlign.Right
都将向右侧对齐。
阴影
通过 style
参数,您可以设置一个类型为 TextStyle
的对象并配置多个参数,例如阴影。Shadow
会接收阴影颜色、偏移量或相对于 Text
所在的位置和模糊半径(用来控制模糊效果)。
@Preview(showBackground = true)
@Composable
fun TextShadow() {
val offset = Offset(5.0f, 10.0f)
Text(
text = "Hello world!",
style = TextStyle(
fontSize = 24.sp,
shadow = Shadow(
color = Color.Blue,
offset = offset,
blurRadius = 3f
)
)
)
}
处理字体
Text
有一个 fontFamily
参数,用于设置可组合项中使用的字体。默认情况下,系统会添加 Serif、Sans Serif、等宽和 Cursive 字体系列:
@Composable
fun DifferentFonts() {
Column {
Text("Hello World", fontFamily = FontFamily.Serif)
Text("Hello World", fontFamily = FontFamily.SansSerif)
}
}
您可以使用 fontFamily
属性来处理 res/font
文件夹中定义的自定义字体和字型:
font 文件夹的图示" class="l10n-absolute-url-src screenshot" l10n-attrs-original-order="src,alt,width,class" src="https://developer.android.com/static/images/jetpack/compose/text-font-folder.png" width="400" />
此示例展示了如何根据这些字体文件以及使用 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)
}
如需了解如何在整个应用中设置排版,请参阅主题文档。
文字中包含多种样式
如需在同一 Text
可组合项中设置不同的样式,必须使用 AnnotatedString
,该字符串可使用任意注解样式加以注解。
AnnotatedString
是一个数据类,其中包含:
- 一个
Text
值 - 一个
SpanStyleRange
的List
,等同于位置范围在文字值内的内嵌样式 - 一个
ParagraphStyleRange
的List
,用于指定文字对齐、文字方向、行高和文字缩进样式
TextStyle
用于 Text
可组合项,而 SpanStyle
和 ParagraphStyle
用于 AnnotatedString
。
SpanStyle
和 ParagraphStyle
之间的区别在于,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")
}
)
}
我们可以用相同的方式设置 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)
}
includeFontPadding 和 lineHeight API
includeFontPadding
是一个旧版属性,它根据第一行文字顶部和最后一行文字底部的字体指标添加额外的内边距。
在 Compose 1.2.0 中,includeFontPadding 默认设为 true。
现在,我们建议使用 Compose 1.2.0 中已废弃的 API PlatformTextStyle
将 includeFontPadding
设置为 false(这将移除多余的内边距),然后进一步调整文字。
配置 lineHeight
的功能并不新鲜。从 Android Q 就开始提供此功能。您可以使用 lineHeight
参数为 Text
配置 lineHeight
,该参数可在每行文本中分布行高。然后,您可以使用新的 LineHeightStyle API
来进一步配置此文本的对齐方式,并移除空格。
为了提高精确度,您可能需要使用文本单位“em”(相对字体大小)而不是“sp”(缩放后的像素)来调整 lineHeight
。如需详细了解如何选择适当的文本单位,请点击此处。

@Composable
fun AlignedText() {
Text(
text = myText,
style = LocalTextStyle.current.merge(
TextStyle(
lineHeight = 2.5.em,
platformStyle = PlatformTextStyle(
includeFontPadding = false
),
lineHeightStyle = LineHeightStyle(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None
)
)
)
)
}
除了调整 lineHeight
以外,您现在还可以使用 LineHeightStyle
实验性 API(LineHeightStyle.Alignment
和 LineHeightStyle.Trim
)来进一步设置文本居中和样式。(includeFontPadding
必须设置为 false 才能让 Trim 发挥作用)。Alignment 和 Trim 会使用在文本行之间测量到的空间,以便更合理地将其分配给所有行,包括单行文本和文本块的第一行。
LineHeightStyle.Alignment
定义了如何在行高提供的空间内对齐行。在每行中,您可以将文本与顶部、底部、中心或按比例对齐。然后,LineHeightStyle.Trim
可允许您从文本首行顶部或末行底部移除多余空格,这将通过任何 lineHeight
和 Alignment 调整生成。以下示例展示了当对齐居中时 (LineHeightStyle.Alignment.Center
),多行文本在各种 LineHeightStyle.Trim
配置下的外观。
![]() |
![]() |
LineHeightStyle.Trim.None | LineHeightStyle.Trim.Both |
![]() |
![]() |
LineHeightStyle.Trim.FirstLineTop | LineHeightStyle.Trim.LastLineBottom |
如需详细了解此变更的背景信息、includeFontPadding 在 View 系统中的工作原理、我们对 Compose 所做的更改以及新的 LineHeightStyle
API,请参阅修复 Compose Text 中的字体内边距问题博文。
主题
如需使用应用主题进行文字样式设置,请参阅主题文档。
用户互动
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")
}
}
}
获取点击文字的位置
如需监听 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
实现分为两个级别:
TextField
是 Material Design 实现。我们建议您选择此实现,因为它遵循的是 Material Design 指南:BasicTextField
允许用户通过硬件或软件键盘编辑文本,但没有提供提示或占位符等装饰。
@Composable
fun SimpleFilledTextFieldSample() {
var text by remember { mutableStateOf("Hello") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") }
)
}
@Composable
fun SimpleOutlinedTextFieldSample() {
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") }
)
}
设置 TextField 样式
TextField
和 BasicTextField
共用许多可对它们进行自定义的常用参数。如需查看 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,建议您使用 TextField
而不是 BasicTextField
。但是,在构建无需 Material 规范中的装饰的设计时,应使用 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' }
}
)
}
如需在清理文本时控制光标位置,请使用 TextField
的 TextFieldValue
重载作为状态的一部分。
可下载字体
从 Compose 1.2-alpha07 开始,您可以使用 Compose 应用中的可下载字体 API 异步下载 Google 字体,并在应用中使用它们。
目前不支持自定义提供程序提供的可下载字体。
以程序化方式使用可下载字体
如需从您的应用内以程序化方式下载字体,请执行以下步骤:
- 添加依赖项:
Groovy
dependencies { ... implementation "androidx.compose.ui:ui-text-google-fonts:1.3.0" }
Kotlin
dependencies { ... implementation("androidx.compose.ui:ui-text-google-fonts:1.3.0") }
- 使用 Google Fonts 的凭据初始化
GoogleFont.Provider
。@OptIn(ExperimentalTextApi::class) val provider = GoogleFont.Provider( providerAuthority = "com.google.android.gms.fonts", providerPackage = "com.google.android.gms", certificates = R.array.com_google_android_gms_fonts_certs )
提供程序收到的参数为:- 针对 Google Fonts 的字体提供程序授权。
- 用于验证提供程序身份的字体提供程序软件包。
- 用于验证提供程序身份的一系列证书哈希集。您可以在 JetChat 示例应用的
font_certs.xml
文件中找到 Google Fonts 提供程序所需的哈希。
ExperimentalTextApi
注解,才能在应用中使用可下载字体 API。 - 按如下方法定义
FontFamily
:import androidx.compose.ui.text.googlefonts.GoogleFont import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.googlefonts.Font val fontName = GoogleFont("Lobster Two") val fontFamily = FontFamily( Font(googleFont = fontName, fontProvider = provider) )
您可以查询字体的其他参数,例如使用FontWeight
和FontStyle
查询字体的粗细和样式,代码如下:import androidx.compose.ui.text.googlefonts.GoogleFont import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.googlefonts.Font val fontName = GoogleFont("Lobster Two") val fontFamily = FontFamily( Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold, style = FontStyle.Italic) )
- 将
FontFamily
配置为用于 Text 可组合函数,这样就可以了。Text( fontFamily = fontFamily, text = "Hello World!" )
FontFamily
。val MyTypography = Typography( body1 = TextStyle( fontFamily = fontFamily, fontWeight = FontWeight.Normal, fontSize = ... ), body2 = TextStyle( fontFamily = fontFamily, fontWeight = FontWeight.Bold, letterSpacing = ... ), h4 = TextStyle( fontFamily = fontFamily, fontWeight = FontWeight.SemiBold ... ), ...
然后,将 Typography 设为应用的主题:MyAppTheme( typography = MyTypography ) { ...
如需通过 Material3 在 Compose 中实现可下载字体的应用示例,请务必查看 JetChat 示例应用。
回退字体
您可以为字体定义回退链,以防字体无法正确下载。例如,如果您对可下载字体有如下定义:
import androidx.compose.ui.text.googlefonts.Font val fontName = GoogleFont("Lobster Two") val fontFamily = FontFamily( Font(googleFont = fontName, fontProvider = provider), Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold) )
您可以为两种粗细字体定义默认值,如下所示:
import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.googlefonts.Font val fontName = GoogleFont("Lobster Two") val fontFamily = FontFamily( Font(googleFont = fontName, fontProvider = provider), Font(resId = R.font.my_font_regular), Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold), Font(resId = R.font.my_font_regular_bold, weight = FontWeight.Bold) )
请务必添加正确的导入。
如上所示地定义 FontFamily
会创建包含两个链的 FontFamily
,每种粗细对应一个链。加载机制将首先尝试解析在线字体,然后解析本地 R.font
资源文件夹中的字体。
调试实现
您可以定义调试协程处理程序,以帮助您验证字体是否正确下载。该处理程序可提供在字体无法异步加载时要执行的操作。
首先,创建一个 CoroutineExceptionHandler
。
val handler = CoroutineExceptionHandler { _, throwable ->
// process the Throwable
Log.e(TAG, "There has been an issue: ", throwable)
}
然后将其传递给 createFontFamilyResolver
方法,以便让解析器使用新的处理程序:
CompositionLocalProvider(
LocalFontFamilyResolver provides createFontFamilyResolver(LocalContext.current, handler)
) {
Column {
Text(
text = "Hello World!",
style = MaterialTheme.typography.body1
)
}
}
您还可以使用相应提供程序提供的 isAvailableOnDevice
API 来测试提供程序是否可用以及证书是否正确配置。为此,您可以调用 isAvailableOnDevice
方法,如果提供程序配置不正确,该方法会返回 false。
val context = LocalContext.current
LaunchedEffect(Unit) {
if (provider.isAvailableOnDevice(context)) {
Log.d(TAG, "Success!")
}
}
注意事项
Google Fonts 需要几个月的时间才能在 Android 上推出新的字体。
将字体添加到 fonts.google.com 与(在 View 系统和 Compose 中)可通过可下载字体 API 获取字体之间存在时间差。新添加的字体可能无法在您的应用中加载,并抛出 IllegalStateException
。为了帮助开发者识别此错误,而不是其他类型的字体加载错误,我们在 Compose 中针对该异常添加了描述性消息,并添加此类更改。