对应Demo: RichTextDemo | 难度: 中级 | 关键词: Span, ContainerSpan, ImageSpan, textBackgroundStyle, 富文本“一段文字里要有红色加粗、蓝色斜体、删除线、还有个图标和可点击的链接”——产品经理甩出这个需求的时候我第一反应是想用WebView加载HTML。后来发现HarmonyOS NEXT的Text组件配合Span/ImageSpan/ContainerSpan就能搞定大部分场景性能比WebView好得多只是API有些地方跟直觉不太一样特别是ContainerSpan那个背景样式属性名坑了不少人。Span同一段文字多种样式最基础的富文本需求一行文字里不同部分有不同样式。传统的Text组件只能设一种fontSize/fontColor想要混合样式就得用SpanText(){Span(这是).fontSize(14).fontColor(#333333)Span(红色加粗).fontSize(14).fontColor(#d32f2f).fontWeight(FontWeight.Bold)Span(和).fontSize(14).fontColor(#333333)Span(蓝色斜体).fontSize(14).fontColor(#1a73e8).fontStyle(FontStyle.Italic)Span(以及).fontSize(14).fontColor(#333333)Span(删除线).fontSize(14).fontColor(#888888).decoration({type:TextDecorationType.LineThrough,color:#888888})Span(的混排。).fontSize(14).fontColor(#333333)}Span的写法很直觉Text()里放多个Span()每个Span独立设置样式。支持的样式属性包括fontSize、fontColor、fontWeight、fontStyle、decoration下划线/删除线。注意Span不是组件是Text的子元素所以它没有onClick不Span其实支持onClick但体验上有点特殊——它是文字级别的点击没有视觉反馈没有按压态需要你自己通过decoration或者fontColor的变化来暗示可点击性。这点后面讲可点击段落的时候再展开。一个容易忽略的细节Span之间不要有空格和换行。ArkTS的声明式语法允许你在Span之间换行写但如果你不小心在两个Span之间加了一个空格字符或者换行符那个空白会被渲染出来导致文字间出现意外的空格。所以Span要么紧贴着写要么刻意控制空白字符。ContainerSpan给一组文字加背景Span只能控制文字样式如果你想要一段文字带背景色比如高亮标签效果就需要ContainerSpanText(){Span(外层文字 ).fontSize(14).fontColor(#333333)ContainerSpan(){Span(背景高亮组).fontSize(14).fontColor(#ffffff)}.textBackgroundStyle({color:#1a73e8,radius:4})Span( 继续外层).fontSize(14).fontColor(#333333)}ContainerSpan的用法它是一个容器里面放Span或其他行内元素然后通过.textBackgroundStyle()给整个容器加背景。注意API名字是textBackgroundStyle不是backgroundStyle也不是backgroundColor——这个属性名我踩过坑写成了backgroundStyle发现完全不生效查了文档才发现是textBackgroundStyle。属性值接受{ color, radius }color是背景色radius是圆角半径。ContainerSpan的本质是给一组行内元素统一加背景。你可以把它理解为HTML的span stylebackground-color: blue; border-radius: 4px但它比HTML span更局限——只支持背景色和圆角不支持边框、内边距等。如果你的设计稿要求标签有borderContainerSpan做不到得考虑别的方案。另外一个注意点ContainerSpan里的Span和外面的Span共享基线对齐。如果你给ContainerSpan里的Span设了不同的fontSize基线会偏移效果可能不如预期。ContainerSpan适合整组文字同字号不同背景色的场景不适合字号差异太大的组合。ImageSpan文字中间插图片这是富文本里最实用的功能之一——在文字行内插入图标或小图片比如✓ 认证通过这种通知样式Text(){Span(收到 ).fontSize(14).fontColor(#333333)ImageSpan($r(sys.symbol.checkmark_circle_fill)).width(18).height(18).verticalAlign(ImageSpanAlignment.CENTER)Span( 认证通过通知).fontSize(14).fontColor(#34a853).fontWeight(FontWeight.Medium)}ImageSpan有几个必须遵守的规则第一必须指定width和height。这不是可选的是强制的。如果你不写width/heightImageSpan不会报错但图片不显示——就是一片空白。这个坑非常隐蔽因为没有任何提示你以为图片资源没加载其实是尺寸没设。第二verticalAlign建议用CENTER。ImageSpan默认对齐方式是BASELINE图片底边跟文字基线对齐。但这在大多数场景下看起来图片偏下了——因为图片的视觉中心比基线高。用verticalAlign(ImageSpanAlignment.CENTER)让图片垂直居中对齐文字视觉效果更好。**第三图片资源用r()引用系统符号。∗∗上面例子用了‘r()引用系统符号。** 上面例子用了r()引用系统符号。∗∗上面例子用了‘r(‘sys.symbol.checkmark_circle_fill’)这是HarmonyOS的系统符号图标不需要自己准备图片资源。常用的系统符号还有exclamationmark_triangle_fill警告、star_fill星标等。如果要用自己的图片就用$r(‘app.media.xxx’)或者直接传URI。复杂图文混排的例子Text(){ImageSpan($r(sys.symbol.star_fill)).width(16).height(16).verticalAlign(ImageSpanAlignment.CENTER)Span( 精选推荐 ).fontSize(16).fontColor(#e65100).fontWeight(FontWeight.Bold)ImageSpan($r(sys.symbol.star_fill)).width(16).height(16).verticalAlign(ImageSpanAlignment.CENTER)Span(\n)Span(HarmonyOS NEXT 完全突破了传统移动操作系统的限制从内核到框架全面重构。).fontSize(13).fontColor(#333333).lineHeight(22)Span(\n)Span(—— 摘自官方介绍).fontSize(11).fontColor(#888888).fontStyle(FontStyle.Italic)}这个例子展示了图标标题正文引用的完整混排。注意用Span(\n)来换行——在Text()的子元素里换行不是自动的需要显式插入换行Span。可点击文字段落用户协议风格注册页面的请阅读《用户协议》和《隐私政策》是最经典的富文本交互需求——普通文字带下划线的可点击链接。Span支持onClick配合decoration可以做出这个效果Text(){Span(请阅读).fontSize(14).fontColor(#333333)Span(《用户协议》).fontSize(14).fontColor(#1a73e8).decoration({type:TextDecorationType.Underline,color:#1a73e8}).onClick((){// 跳转到用户协议页面})Span(和).fontSize(14).fontColor(#333333)Span(《隐私政策》).fontSize(14).fontColor(#1a73e8).decoration({type:TextDecorationType.Underline,color:#1a73e8}).onClick((){// 跳转到隐私政策页面})}这里有两个要点decoration设下划线TextDecorationType.Underline配合fontColor相同的color做出经典链接样式onClickSpan的onClick可以直接用不需要额外的手势处理但Span的onClick有一个体验问题没有按压态。普通Button按下去有颜色变化反馈Span点击时文字没有任何视觉变化。用户可能不确定自己点没点上。解决方案是在onClick里手动改变Span的fontColor需要用State变量控制或者干脆用ContainerSpan包一层点击时改变背景色。不过对于用户协议这种低频点击场景没有按压态其实问题不大。TextSpan方案 vs RichText组件HarmonyOS还提供了RichText组件可以直接渲染HTML字符串。看起来很方便但有明显的局限方案交互图片性能适用场景TextSpan支持(Span.onClick)ImageSpan高标签/通知/短文本RichText有限(仅链接点击)img标签低HTML内容展示Web完整完整低完整HTML页面RichText只支持有限的HTML标签子集——基本的b、i、u、a、img可以复杂的CSS样式和JavaScript完全不支持。而且RichText的onClick只支持a标签的链接点击想做更灵活的交互是不行的。性能方面TextSpan是原生组件渲染RichText需要解析HTML再渲染开销更大。如果你只是做几种样式的文字混排不要用RichText——用TextSpan既快又灵活。Web组件是终极方案什么HTML都能渲染但代价是性能开销大、内存占用高、交互延迟。除非你真的要展示一个完整的网页否则别碰Web组件。坑点汇总坑1ImageSpan必须指定width和height。不指定不报错但图片不显示这是最隐蔽的坑。每次写ImageSpan先写width/height再写其他属性养成习惯。坑2ContainerSpan的背景属性名是textBackgroundStyle。不是backgroundStyle不是backgroundColor也不是background。写错属性名不会报错只是不生效——然后你查半天为什么背景没出来。坑3Span的onClick没有按压态反馈。如果需要视觉反馈手动用State变量控制fontColor或背景色变化。坑4ContainerSpan的baselineOffset效果不稳定。不同API版本表现可能不同如果发现对齐异常尝试调整ContainerSpan内部Span的fontSize保持一致。坑5ImageSpan的verticalAlign建议用CENTER。默认BASELINE对齐在大多数场景下图片偏下CENTER对齐视觉效果更好。坑6RichText组件只支持有限HTML标签子集。不要期望RichText能渲染任意HTML它只是一个轻量级的HTML展示组件。复杂HTML请用Web组件。写在最后TextSpanImageSpanContainerSpan这套组合拳覆盖了90%的富文本需求记住ImageSpan必须设尺寸、ContainerSpan用textBackgroundStyle、Span的onClick没按压态这三个点少走很多弯路。