SpannableString是 Android 中的 富文本处理类,它允许在字符串的不同部分应用不同的样式(颜色、字体、点击事件、图片等)。
相比SpannableStringBuilder,SpannableString是不可变的,创建后不能修改内容,但可以添加Span(样式)。
二者区别感觉可以类比 String 和 StringBuilder
常用 Span 类型
- **文本样式**:`StyleSpan`(粗体、斜体)、`TypefaceSpan`(字体)。
- **颜色与背景**:`ForegroundColorSpan`(文字颜色)、`BackgroundColorSpan`。
- **交互功能**:`ClickableSpan`(点击事件)、`URLSpan`(超链接)。
- **布局调整**:`RelativeSizeSpan`(字体缩放)、`UnderlineSpan`(下划线)。
应用场景
文本高亮在搜索功能中,将关键词标记为特殊颜色(示例):
text.setSpan(BackgroundColorSpan(Color.YELLOW), keywordStart, keywordEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)混合样式文本实现同一行文字中部分加粗、部分变色,如“错误:数据加载失败”中的“错误”为红色加粗。
可点击文本结合ClickableSpan实现超链接或按钮功能:
text.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) { /* 处理点击 */ }
}, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)动态文本拼接使用SpannableStringBuilder(可变版本)灵活组合多个 Span,例如聊天界面中混合用户名、内容和表情。
使用注意事项
- 性能优化避免在长文本中频繁添加 Span(如列表项),建议复用 Spannable 对象或使用
SpannableStringBuilder。 - 索引范围
start和end参数需严格符合字符串的索引区间(从 0 开始),否则会抛出异常。 - 交互设置
ClickableSpan需为TextView设置movementMethod = LinkMovementMethod.getInstance()。- 避免与
TextView的默认点击事件冲突。
与 SpannableStringBuilder 的区别
| 特性 | SpannableString | SpannableStringBuilder |
|---|---|---|
| 可变性 | 不可变 | 可变(支持追加、插入) |
| 适用场景 | 单次设置固定文本 | 动态构建复杂文本 |
| 性能 | 较高 | 较低(频繁修改时) |
项目中的应用
通过SpannableStringBuilder构造标签分隔符|。
主要逻辑:
SpannableStringBuilder用于构造富文本- 遍历
tags:- 追加
tag文字 - 如果不是最后一个标签,则插入 特殊符号
\uFFFC作为占位符,并 添加图片Span
- 追加
- 最后转换为
SpannableString并返回。
**(1) **getTagsSpan方法解析
private fun getTagsSpan(tags: List<String>): SpannableString {
val spanTmp = SpannableStringBuilder()
val suffix = "\uFFFC" // 特殊字符(占位符)
// 计算图片高度(稍小于文字)
val imageHeight = getTextHeight(DeviceUtils.ScreenInfo.dp2px(context, TAG_FONT_SIZE).toFloat()).toInt() - DIFactory.dp2px(2f)
// 遍历 tags,每个 tag 之间插入图片
tags.forEachIndexed { index, tag ->
spanTmp.append(tag) // 添加文本
if (index < tags.size - 1) { // 不是最后一个 tag
spanTmp.append(suffix) // 添加占位符
spanTmp.addImageSpan(start = spanTmp.length - 1, imageHeight = imageHeight) // 替换成图片
}
}
return SpannableString(spanTmp) // 返回 SpannableString
}🔹 作用:
- 遍历
tags列表,依次添加tag文本。 - 如果不是最后一个
tag,就插入一个占位符\uFFFC并用ImageSpan替换,形成“文本-图片-文本”的效果。 - 最后返回
SpannableString,保证不可变性。
**(2) **addImageSpan方法解析
private fun SpannableStringBuilder.addImageSpan(
start: Int,
imageHeight: Int? = null,
end: Int? = null,
flags: Int = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
) {
// 获取 drawable 资源
val drawable = getVideoScaledDrawableRes(com.baidu.searchbox.video.feedflow.component.R.drawable.video_flow_collection_panel_item_no_select_landscape_new) ?: return
// 计算 drawable 的高度
val drawableHeight = imageHeight ?: drawable.intrinsicHeight
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawableHeight)
// 创建 ImageSpan
val imageSpan = SummaryEnrichedSpanHelper.VerticalImageSpan(drawable)
// 将图片 Span 替换到字符串中
setSpan(imageSpan, start, end ?: (start + 1), flags)
}🔹 作用:
start位置的\uFFFC占位符替换为图片(ImageSpan)。setBounds()设置Drawable的大小,保证与文本匹配。
**(3) **getTextHeight方法解析
private fun getTextHeight(textSize: Float): Float {
val paint = Paint()
paint.textSize = textSize
val fontMetrics = paint.fontMetrics
return fontMetrics.descent - fontMetrics.ascent
}🔹 作用:计算字体高度,用于 调整图片高度,使得图片和文字对齐。
代码整体解析
getTagsSpan(tags: List<String>)创建SpannableString,在tags之间插入ImageSpan。addImageSpan()将\uFFFC** 替换成**ImageSpan,使文字与图片混排。getTextHeight()计算字体高度,使得图片与文本对齐。
解析带 emoji 的 subTitle
更新: 2026-03-15 17:39:30
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/fvbllqtabo4r9mtr