5、文本与样式:Text组件、字体与颜色、Style样式、AnnotatedString富文本、TextField输入框

文本是UI的灵魂。不管你的界面设计得多花哨,用户最终看的还是上面的字。Jetpack Compose 在文本处理上给了我不少惊喜——它把传统 Android 里那些繁琐的 SpannableString、自定义字体加载,全都浓缩成了声明式的 API。今天我们就来彻底搞懂它。

5.1 Text 组件:最基础的展示单元

Text 组件是 Compose 里最常用的组件之一。用法极其简单:

@Composable
fun SimpleText() {
    Text(text = "Hello, Compose!")
}

但别被它的外表骗了。Text 背后藏着大量可配置的参数。我整理了一份常用参数表:

参数 类型 说明
text String / AnnotatedString 显示的文本内容
modifier Modifier 布局修饰符
color Color 文字颜色
fontSize TextUnit 字体大小(sp)
fontStyle FontStyle 斜体等
fontWeight FontWeight 字重(粗体等)
fontFamily FontFamily 字体族
letterSpacing TextUnit 字间距
textDecoration TextDecoration 下划线、删除线
textAlign TextAlign 对齐方式
lineHeight TextUnit 行高
overflow TextOverflow 溢出处理
maxLines Int 最大行数

举个例子,一个带样式的标题:

Text(
    text = "欢迎来到 Compose 世界",
    fontSize = 24.sp,
    fontWeight = FontWeight.Bold,
    color = Color(0xFF1A73E8),
    letterSpacing = 2.sp,
    maxLines = 1,
    overflow = TextOverflow.Ellipsis
)
💡 我的习惯: 在写 Text 时,我通常会先确定 maxLines 和 overflow。为什么呢?因为在实际项目中,后台返回的文本长度经常超出预期。如果不限制行数,布局可能会被撑得很难看。我曾经在一个用户昵称展示的地方忘了加 maxLines,结果一个超长昵称把整个卡片都撑变形了……从那以后,凡是展示性文本,我都会顺手加上这两个参数。

5.2 字体与颜色:让文字有「性格」

字体和颜色是文字风格的基石。Compose 提供了非常灵活的方式来控制它们。

5.2.1 颜色

颜色可以直接用 Color 类指定:

Text(
    text = "红色警告",
    color = Color.Red
)

// 使用 ARGB
Text(
    text = "自定义颜色",
    color = Color(0xFFFF5722)
)

更推荐的做法是使用 MaterialTheme 的颜色系统:

Text(
    text = "主色调文字",
    color = MaterialTheme.colorScheme.primary
)

Text(
    text = "次要文字",
    color = MaterialTheme.colorScheme.onSurfaceVariant
)
⚠️ 注意: 不要硬编码颜色值。我曾经在一个项目里看到几十处 Color(0xFF....) 的写法,后来 UI 改版要换主题色,改得我头皮发麻。请一定使用 MaterialTheme 的颜色系统,或者至少抽取成常量。

5.2.2 字体

Compose 默认使用系统字体。如果你想用自定义字体,需要先把它放到 res/font 目录下,然后这样加载:

// 在 res/font/ 下放置 your_font.ttf
val customFont = FontFamily(
    Font(R.font.your_font, FontWeight.Normal),
    Font(R.font.your_font_bold, FontWeight.Bold)
)

Text(
    text = "自定义字体",
    fontFamily = customFont,
    fontWeight = FontWeight.Bold
)

如果你需要下载字体(比如 Google Fonts),可以用 Compose 的字体下载 API:

val fontFamily = FontFamily(
    GoogleFont("Roboto"),
    Font(
        googleFont = GoogleFont("Roboto"),
        fontProvider = GoogleFont.Provider(
            providerAuthority = "com.google.android.gms.fonts",
            providerPackage = "com.google.android.gms",
            certificates = R.array.com_google_android_gms_fonts_certs
        )
    )
)

嗯,这个写法有点啰嗦。我个人更倾向于把常用的字体封装成一个全局变量,避免到处重复这段代码。

5.3 Style 样式:复用文本样式

如果你有多个 Text 组件需要相同的样式,一个个写参数就太累了。这时候就该 Style 出场了。

val titleStyle = TextStyle(
    fontSize = 20.sp,
    fontWeight = FontWeight.Bold,
    color = Color(0xFF333333),
    letterSpacing = 1.sp,
    lineHeight = 28.sp
)

val bodyStyle = TextStyle(
    fontSize = 14.sp,
    color = Color(0xFF666666),
    lineHeight = 20.sp
)

// 使用
Text(text = "标题", style = titleStyle)
Text(text = "正文内容...", style = bodyStyle)

更高级的用法是结合 MaterialTheme 的 Typography:

// 在 Theme 中定义
val typography = Typography(
    titleLarge = TextStyle(
        fontSize = 22.sp,
        fontWeight = FontWeight.Bold
    ),
    bodyMedium = TextStyle(
        fontSize = 14.sp,
        lineHeight = 20.sp
    )
)

// 使用
Text(
    text = "标题",
    style = MaterialTheme.typography.titleLarge
)
🎯 核心思路: 把样式定义在 Theme 层,所有组件统一引用。这样当设计规范更新时,你只需要改一个地方。我在实际项目中深有体会——UI 设计师改样式是家常便饭,没有统一管理的话,改起来就是噩梦。

5.4 AnnotatedString 富文本:一段文字,多种样式

有时候你需要在一段文字里混用不同的样式。比如「注意:请仔细阅读以下条款」。传统 Android 用 SpannableString,Compose 里用 AnnotatedString。

val annotatedString = buildAnnotatedString {
    // 普通文字
    append("注意:")
    
    // 加粗红色文字
    withStyle(
        SpanStyle(
            fontWeight = FontWeight.Bold,
            color = Color.Red
        )
    ) {
        append("请仔细")
    }
    
    // 普通文字
    append("阅读以下条款")
    
    // 添加点击事件
    pushStringAnnotation(tag = "link", annotation = "https://example.com")
    withStyle(
        SpanStyle(
            color = Color.Blue,
            textDecoration = TextDecoration.Underline
        )
    ) {
        append("点击这里")
    }
    pop()
}

Text(text = annotatedString)

// 处理点击
ClickableText(
    text = annotatedString,
    onClick = { offset ->
        annotatedString.getStringAnnotations(
            tag = "link",
            start = offset,
            end = offset
        ).firstOrNull()?.let { annotation ->
            // 打开链接
        }
    }
)

这里有个细节要注意:pushStringAnnotationpop 必须成对出现,否则会报错。我刚开始用的时候漏掉了 pop,结果编译通过了但运行时点击没反应,排查了半天才发现是这个问题。

💡 避坑指南: 我曾经在富文本里嵌套了多层样式,结果代码变得又长又乱。后来我学乖了——如果样式逻辑太复杂,就拆成多个 Text 组件用 Row 排列,反而更清晰。AnnotatedString 适合「少量、局部」的样式变化,别滥用。

5.5 TextField 输入框:让用户说话

输入框是交互的核心。Compose 提供了两个版本:TextField(Material Design 2 风格)和 OutlinedTextField(带外边框)。

5.5.1 基本用法

var text by remember { mutableStateOf("") }

OutlinedTextField(
    value = text,
    onValueChange = { text = it },
    label = { Text("用户名") },
    placeholder = { Text("请输入用户名") },
    leadingIcon = {
        Icon(Icons.Default.Person, contentDescription = null)
    },
    modifier = Modifier.fillMaxWidth()
)

注意:valueonValueChange 是必须的。Compose 的输入框是受控组件,数据流向是单向的——用户输入触发 onValueChange,你更新状态,状态再驱动 UI 刷新。

5.5.2 常用参数

参数 说明
label 标签(浮动提示)
placeholder 占位提示
leadingIcon / trailingIcon 前置/后置图标
isError 是否显示错误状态
supportingText 辅助说明文字(常用于错误提示)
keyboardOptions 键盘类型(数字、邮箱等)
keyboardActions 键盘动作(如「完成」按钮点击)
visualTransformation 视觉变换(如密码遮罩)

5.5.3 实战:带验证的输入框

var email by remember { mutableStateOf("") }
var isError by remember { mutableStateOf(false) }

OutlinedTextField(
    value = email,
    onValueChange = { 
        email = it
        isError = !it.contains("@")
    },
    label = { Text("邮箱") },
    isError = isError,
    supportingText = {
        if (isError) {
            Text("请输入有效的邮箱地址", color = MaterialTheme.colorScheme.error)
        }
    },
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
    singleLine = true,
    modifier = Modifier.fillMaxWidth()
)
⚠️ 注意: 密码输入框记得设置 visualTransformation = PasswordVisualTransformation(),否则密码会明文显示。另外,singleLine = true 可以限制输入框为单行,避免用户输入大量文本导致布局异常。

5.6 知识体系总览

下面这张图概括了本章的核心内容,我把它画成了 SVG,方便你对照复习:

文本与样式 Text 组件 基础展示 参数配置 字体与颜色 Color / FontFamily MaterialTheme 颜色系统 Style 样式 TextStyle 复用 Typography 主题化 AnnotatedString 富文本 SpanStyle 局部样式 ClickableText 点击事件 TextField 输入框 受控组件模式 验证与键盘配置 实战技巧 maxLines + overflow 统一样式管理 核心:声明式、可复用、主题化

文本处理看似简单,但细节很多。从基础的 Text 展示,到 Style 复用,再到 AnnotatedString 富文本和 TextField 交互,每一步都有值得深挖的地方。我个人觉得,掌握好这些内容,你的 Compose UI 功底就已经超过大多数人了。

最后提醒一句:多写、多试。光看文档是不够的,打开 Android Studio,把上面的代码敲一遍,改改参数看看效果。实践出真知,这话在编程领域永远不过时。


公众号:蓝海资料掘金营,微信deep3321