1. Android TextView多色显示实现方案在Android应用开发中TextView是最基础的UI组件之一。常规使用中我们通常通过android:textColor属性为整个TextView设置单一颜色。但在实际项目需求中经常遇到需要在一个TextView内显示不同颜色文字的场景比如聊天应用中的关键字高亮商品价格显示原价灰色现价红色表单中的必填项星号标记文本中的超链接特殊显示这种需求看似简单但如果采用多个TextView拼接的方案不仅会增加布局复杂度还会破坏文本的整体性和排版效果。Android系统其实已经为我们提供了完善的解决方案——SpannableString。2. SpannableString核心原理解析2.1 Span机制工作原理SpannableString是CharSequence接口的实现类它允许我们在字符序列上附加各种span标记。这些标记可以改变字符的显示样式、颜色、大小等属性而无需修改原始文本内容。关键类解析SpannableString可附加span的不可变字符序列SpannableStringBuilder可变的SpannableString支持文本修改ForegroundColorSpan改变文本前景色文字颜色BackgroundColorSpan改变文本背景色AbsoluteSizeSpan改变文本绝对大小RelativeSizeSpan改变文本相对大小2.2 颜色设置方案对比方案优点缺点适用场景多个TextView实现简单布局复杂排版困难简单场景Html.fromHtml支持HTML标签性能较差功能有限简单富文本SpannableString功能强大性能好代码稍复杂复杂样式需求3. 具体实现步骤详解3.1 基础实现代码val text 原价¥999 现价¥599 val spannable SpannableString(text) // 设置原价部分为灰色 spannable.setSpan( ForegroundColorSpan(Color.GRAY), 0, // 起始位置 4, // 结束位置(不包含) Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) // 设置现价部分为红色 spannable.setSpan( ForegroundColorSpan(Color.RED), 7, // 起始位置 11, // 结束位置(不包含) Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) textView.text spannable3.2 参数详解setSpan()方法的四个参数要设置的Span对象如ForegroundColorSpan起始字符索引包含结束字符索引不包含标志位控制span行为常用标志位SPAN_EXCLUSIVE_EXCLUSIVE不包含起始和结束位置SPAN_INCLUSIVE_INCLUSIVE包含起始和结束位置SPAN_EXCLUSIVE_INCLUSIVE不包含起始但包含结束位置3.3 动态计算位置对于动态文本可以使用字符串方法来计算位置val priceText 促销价¥${product.price} val spannable SpannableString(priceText) // 找到¥符号的位置 val symbolIndex priceText.indexOf(¥) spannable.setSpan( ForegroundColorSpan(Color.RED), symbolIndex, priceText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE )4. 高级应用与优化技巧4.1 组合使用多种Span可以同时应用多种Span实现复杂效果val text VIP专享折扣 val spannable SpannableString(text) // 设置颜色 spannable.setSpan( ForegroundColorSpan(Color.parseColor(#FFD700)), 0, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) // 设置粗体 spannable.setSpan( StyleSpan(Typeface.BOLD), 0, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) // 设置文字大小 spannable.setSpan( RelativeSizeSpan(1.2f), 0, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE )4.2 性能优化建议避免频繁创建Span对象对于静态文本应在初始化时创建SpannableString并缓存使用SpannableStringBuilder需要频繁修改文本时使用可变版本减少不必要的Span每个Span都会增加内存开销重用Span对象对于相同样式的文本可以重用Span实例4.3 常见问题解决方案问题1Span不生效检查起始/结束位置是否正确确认TextView没有设置android:textColor属性覆盖Span效果确保在UI线程更新TextView内容问题2文字测量不准确使用Layout类获取精确的文字位置考虑文字宽度而非字符数来计算位置问题3点击事件处理使用ClickableSpan实现可点击文本记得调用textView.movementMethod LinkMovementMethod.getInstance()5. 实际项目中的应用案例5.1 商品价格显示fun setPriceText(originalPrice: Int, currentPrice: Int) { val text ¥$originalPrice ¥$currentPrice val spannable SpannableString(text) // 原价样式灰色删除线 spannable.setSpan( ForegroundColorSpan(Color.GRAY), 0, originalPrice.toString().length 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) spannable.setSpan( StrikethroughSpan(), 0, originalPrice.toString().length 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) // 现价样式红色加粗 val currentStart originalPrice.toString().length 2 spannable.setSpan( ForegroundColorSpan(Color.RED), currentStart, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) spannable.setSpan( StyleSpan(Typeface.BOLD), currentStart, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) priceTextView.text spannable }5.2 聊天消息关键词高亮fun highlightKeywords(message: String, keywords: ListString) { val spannable SpannableStringBuilder(message) keywords.forEach { keyword - var startIndex 0 while (startIndex message.length) { val index message.indexOf(keyword, startIndex) if (index -1) break spannable.setSpan( ForegroundColorSpan(Color.BLUE), index, index keyword.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) spannable.setSpan( StyleSpan(Typeface.BOLD), index, index keyword.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) startIndex index keyword.length } } messageTextView.text spannable }6. 扩展知识与替代方案6.1 使用HTML实现简单多色文本对于简单场景可以使用Html.fromHtmltextView.text Html.fromHtml( 原价font colorgray¥999/font 现价font colorred¥599/font, Html.FROM_HTML_MODE_COMPACT )注意HTML方案性能较差复杂样式建议仍使用SpannableString6.2 自定义Span实现特殊效果继承ReplacementSpan可以创建完全自定义的文本效果class GradientColorSpan : ReplacementSpan() { override fun draw( canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint ) { val textToDraw text.subSequence(start, end).toString() val gradient LinearGradient( x, y, x paint.measureText(textToDraw), y, Color.RED, Color.BLUE, Shader.TileMode.CLAMP ) paint.shader gradient canvas.drawText(textToDraw, x, y.toFloat(), paint) } // 其他必要方法实现... }6.3 Jetpack Compose中的多色文本在Compose中实现类似效果Text( buildAnnotatedString { withStyle(style SpanStyle(color Color.Gray)) { append(原价¥999) } append( ) withStyle(style SpanStyle(color Color.Red)) { append(现价¥599) } } )7. 调试技巧与工具7.1 调试Span应用使用textView.text.toString()检查原始文本使用(textView.text as Spannable).getSpans()获取所有Span在Android Studio布局检查器中查看TextView属性7.2 性能分析工具Android Profiler监测内存使用使用StrictMode检测UI线程操作Systrace分析渲染性能7.3 单元测试方案Test fun testPriceTextSpans() { val activity ActivityScenario.launch(MainActivity::class.java) activity.onActivity { val spans (it.priceTextView.text as Spannable).getSpans( 0, it.priceTextView.text.length, ForegroundColorSpan::class.java ) assertEquals(2, spans.size) // 验证Span数量 assertEquals(Color.GRAY, spans[0].foregroundColor) // 验证原价颜色 assertEquals(Color.RED, spans[1].foregroundColor) // 验证现价颜色 } }在实际项目中TextView的多色显示是一个看似简单但内涵丰富的技术点。合理使用Spannable不仅可以实现丰富的视觉效果还能保持代码的整洁和性能的高效。特别是在电商、社交、内容类应用中这种技术几乎必不可少。