Text 组件 API 与样式定制

Text 是 Jetpack Compose 中最基础的显示组件,负责在界面中输出字符串。它的样式定制能力覆盖字体、颜色、对齐、行数截断以及富文本标注。本章从参数体系、实现流程、调试手法到性能陷阱,完整覆盖 Text 的实用开发环节。

1. 核心参数体系

Text 组件最常用的参数分为三组:基础文本内容、排版样式、布局约束。以下表格列出关键参数及类型:

参数类型作用
textString / AnnotatedString要显示的文本或富文本
modifierModifier尺寸、点击、装饰等外部布局修饰
styleTextStyle定义字体族、大小、粗细、行高、字间距等
colorColor文字颜色,可覆盖 style 中的颜色
textAlignTextAlign水平对齐方式:Start、End、Center、Justify
maxLinesInt最大行数,超出部分按 overflow 处理
overflowTextOverflow溢出时效果:Clip、Ellipsis、Visible
softWrapBoolean是否允许自动换行(默认 true)

所有样式参数都可以在 style 块中统一设置,也可以拆成独立参数覆盖。优先级:独立参数 > style 内对应字段。

2. 字体、颜色与对齐

字体通过 TextStyle 的 fontFamily 指定,可以设置系统字体或自定义字体族。颜色直接传 Color 对象,支持 Material 颜色体系或十六进制。

基础示例: 使用单一 TextStyle 控制所有样式
@Composable
fun StyledTextDemo() {
    Text(
        text = "Hello Compose",
        style = TextStyle(
            fontFamily = FontFamily.SansSerif,
            fontWeight = FontWeight.Medium,
            fontSize = 18.sp,
            color = Color(0xFF2C3E50),
            textAlign = TextAlign.Center,
            letterSpacing = 0.5.sp
        ),
        modifier = Modifier.fillMaxWidth()
    )
}

对齐不仅影响水平方向,也影响垂直行内基线。注意 TextAlign.Center 只对单行或多行整体水平居中有效;垂直居中需要配合 Modifier.wrapContentHeight(align = Alignment.CenterVertically) 实现。

3. maxLines 与 overflow

当文本过长时,使用 maxLines 限制行数,结合 overflow 决定溢出行为。有三种模式:

  • Clip:直接裁剪超出的部分,无省略号。
  • Ellipsis:在最后一行末尾显示省略号“…”(默认值)。
  • Visible:溢出部分继续绘制,但可能超出父容器边界。
避坑提示: 同时使用 maxLines 和 Modifier.heightIn(max = ...) 时,可能因高度限制导致多余空白。优先用 maxLines 控制行数,行高由行距自动计算。
@Composable
fun TruncatedText() {
    Text(
        text = "这是一段非常长的文本,需要限制在三行以内并显示省略号。",
        maxLines = 3,
        overflow = TextOverflow.Ellipsis,
        style = MaterialTheme.typography.body1
    )
}

4. AnnotatedString:富文本与交互

AnnotatedString 允许在同一个 Text 内对不同片段设置不同样式,甚至添加点击链接。常用构建方式是通过 buildAnnotatedString lambda。

关键 API:
  • append(string) —— 追加普通文本
  • withStyle(style, content) —— 为一段文本应用 TextStyle
  • pushStringAnnotation(tag, value) —— 标记可交互区域
  • pop() —— 结束当前标记
@Composable
fun AnnotatedTextDemo() {
    val annotated = buildAnnotatedString {
        withStyle(SpanStyle(color = Color.Red, fontWeight = FontWeight.Bold)) {
            append("红色粗体")
        }
        append(" ")
        withStyle(SpanStyle(fontStyle = FontStyle.Italic, fontSize = 20.sp)) {
            append("斜体大字")
        }
    }
    Text(text = annotated)
}

要实现字符串内部点击链接,需要配合 ClickableText 组件。示例如下:

@Composable
fun ClickableLinkText() {
    val annotated = buildAnnotatedString {
        pushStringAnnotation(tag = "url", annotation = "https://example.com")
        withStyle(SpanStyle(color = Color.Blue, textDecoration = TextDecoration.Underline)) {
            append("点击访问示例链接")
        }
        pop()
    }
    ClickableText(
        text = annotated,
        onClick = { offset ->
            annotated.getStringAnnotations("url", offset, offset)
                .firstOrNull()?.let { annotation ->
                    // 处理点击事件,例如打开浏览器
                }
        }
    )
}

5. 调试与常见错误

在开发阶段,可以使用 Compose 的布局 inspector 查看 Text 的实际渲染区域。常见错误包括:

  • 字体未生效: 确认 fontFamily 引用的字体文件已放入 res/font 并在 theme 中正确配置。
  • 颜色覆盖失效: 检查是否在 style 中设置了颜色,同时又在组件参数里传了另一个 color,后者优先级更高。
  • maxLines 不生效: 确保没有使用 Modifier.height() 固定高度,否则可能提前截断。
  • AnnotatedString 文字不显示: 检查 buildAnnotatedString 中是否忘记 pop() 或嵌套标记未闭合。

6. 性能与稳定性优化

Text 在编译期会测量字形,但高频重组时可能引发性能问题:

  • 避免在 compose 中实时构建超长 AnnotatedString: 将富文本构建放到 remember 或 derivedStateOf 中,利用快照系统避免重复构建。
  • 字体加载: 使用 FontFamily.Custom 时,确保字体资源在首次渲染前已加载,否则出现延迟闪烁。考虑在 Application 初始化时预加载常用字体。
  • overflow 与软换行: 大量文本且频繁 re-layout 时,可考虑将 softWrap = false 并用水平滚动代替换行重绘,但需权衡可用性。
注意: 在 LazyColumn 中为每个 item 使用样式复杂的 AnnotatedString 时,推荐为每个 item 缓存 AnnotatedString 实例,避免 recomposition 阶段大量对象创建导致 GC 抖动。

7. 本章核心流程结构

下图概括了 Text 组件样式定制的完整流程及内部决策点:

Text 样式定制流程 输入 text / modifier 解析 TextStyle + 独立样式参数 应用 textAlign / maxLines / overflow 普通 String Annotated String 普通 DrawText 渲染 AnnotatedString 分段绘制 布局&压栈到 SlotTable

左上角输入后,经过样式合并、对齐与截断判断,再根据内容类型选择普通绘制或富文本分段绘制,最终合成并提交到 Compose 内部缓存。