实现富文本信息的显示 — HarmonyOS StyledText
项目地址:https://gitcode.com/harmonyos_samples/styledtext 技术栈:HarmonyOS / ArkTS / ArkUI
项目简介
本示例展示了如何在 HarmonyOS 应用中实现富文本信息的显示,涵盖了四个核心场景:高亮显示的超链接文本、自定义表情、图标与文本的组合元素、以及自定义的图文元素。通过自定义 Span 对象和 TextController,开发者可以灵活地在 Text 组件中混合展示不同样式的文本内容,并支持点击交互。
项目的核心亮点在于使用 MutableStyledString 和 CustomSpan 等 ArkUI API,实现了高度可定制的富文本渲染方案。这种方案不仅支持文本样式定制,还能嵌入图片、自定义绘制元素,以及手势交互,为 HarmonyOS 应用的文本展示提供了强大的能力。
功能概览
- 高亮超链接文本:支持话题(#话题)和用户(@用户)的点击跳转
- 自定义表情:在文本中嵌入自定义表情图片
- 图标与文本组合:在文本前添加播放图标,支持视频链接点击
- 自定义图文元素:使用
CustomSpan实现自定义绘制的文本标签

工程结构
entry/src/main/ets/
├── common/
│ └── HandleData.ets # 公共处理方法
├── components/
│ └── MyDrawCustomSpan.ets # 自定义绘制组件
├── mock/
│ └── MockData.ets # mock 测试模拟数据
├── model/
│ └── MyCustomSpan.ets # 数据类型定义
└── pages/
├── CustomizeEmoji.ets # 自定义表情页面
├── Home.ets # 主页面
├── ImageText.ets # 自定义的图文元素页面
├── TitleDetail.ets # 话题详情跳转页面
├── TitleLink.ets # 高亮显示的超链接文本页面
├── UserDetails.ets # 用户详情跳转页面
└── VideoLink.ets # 图标与文本的组合元素页面核心实现解析
数据模型:MyCustomSpan
项目定义了一个统一的 MyCustomSpan 类来表示不同类型的文本片段:
export class MyCustomSpan {
id: number | string; // 文本 Id
content: string; // 文本内容
url?: string; // 可选的跳转链接或类型标识
constructor(id: number | string, content: string, url?: string) {
this.id = id;
this.content = content;
if (url) {
this.url = url;
}
}
}url 字段具有双重作用:
- 当值为页面路径时(如
'pages/TitleDetail'),表示可点击的超链接 - 当值为颜色标识时(如
'green'、'red'),表示自定义绘制标签的颜色类型 - 当不存在时,表示普通文本
公共处理方法:HandleData
HandleData 类提供了将多个 MutableStyledString 合并为最终显示内容的核心方法:
static handleStyledString(styledStrings: MutableStyledString[]): TextController {
let controller: TextController = new TextController();
let paragraphStyledString: MutableStyledString = new MutableStyledString('', []);
// 将每个文本片段的属性字符串追加到段落中
styledStrings.forEach((mutableStyledString: MutableStyledString) => {
paragraphStyledString.appendStyledString(mutableStyledString);
})
controller.setStyledString(paragraphStyledString);
return controller;
}这个方法是所有富文本页面的通用入口,它将分散的文本片段组合成完整的富文本内容,并通过 TextController 控制显示。
场景一:高亮超链接文本(TitleLink)
高亮超链接文本页面展示了如何在文本中嵌入可点击的话题和用户链接。

核心实现:
handleLink(span: MyCustomSpan) {
this.styledStrings.push(new MutableStyledString(span.content, [{
start: 0,
length: span.content.length,
styledKey: StyledStringKey.GESTURE,
styledValue: this.generateClickStyle(span)
}, {
start: 0,
length: span.content.length,
styledKey: StyledStringKey.FONT,
styledValue: this.textAttribute
}]));
}关键点:
- 使用
StyledStringKey.GESTURE设置点击手势 - 使用
StyledStringKey.FONT设置字体样式(颜色、大小) - 通过
GestureStyle的onClick回调处理点击事件
场景二:自定义表情(CustomizeEmoji)
自定义表情页面展示了如何在文本中嵌入表情图片。

核心实现:
handleEmoji(span: MyCustomSpan) {
this.styledStrings.push(new MutableStyledString(new ImageAttachment({
resourceValue: EMOJI_DATA.get(span.content),
size: {
width: 16,
height: 16
}
})));
}关键点:
- 使用
ImageAttachment嵌入图片资源 - 通过
EMOJI_DATAMap 映射表情文字到对应的图片资源 - 设置图片尺寸为 16x16 像素
场景三:图标与文本组合(VideoLink)
图标与文本组合页面展示了如何在文本前添加播放图标,并支持点击跳转。

核心实现:
handleVideoLink(span: MyCustomSpan) {
// 添加播放图标
this.styledStrings.push(new MutableStyledString(new ImageAttachment({
resourceValue: $r('app.media.play_round_rectangle'),
size: {
width: $r('app.integer.styled_text_video_link_icon_size'),
height: $r('app.integer.styled_text_video_link_icon_size')
},
verticalAlign: ImageSpanAlignment.CENTER,
objectFit: ImageFit.Contain
})));
// 添加可点击的文本
this.styledStrings.push(new MutableStyledString(span.content, [{
start: 0,
length: span.content.length,
styledKey: StyledStringKey.GESTURE,
styledValue: this.generateClickStyle(span)
}, {
start: 0,
length: span.content.length,
styledKey: StyledStringKey.FONT,
styledValue: this.textAttribute
}]));
}关键点:
- 先添加
ImageAttachment图标,再添加文本 - 使用
ImageSpanAlignment.CENTER垂直居中对齐 - 图标和文本分别作为独立的
MutableStyledString添加
场景四:自定义图文元素(ImageText)
自定义图文元素页面展示了如何使用 CustomSpan 实现完全自定义绘制的文本标签。

核心实现:
handleImageText(span: MyCustomSpan) {
let resourceStr = $r('app.media.doc_plaintext_green');
if (span.url === "red") {
resourceStr = $r('app.media.doc_plaintext_red');
}
// 添加文档图标
this.styledStrings.push(new MutableStyledString(new ImageAttachment({
resourceValue: resourceStr,
size: {
width: 13,
height: 13
},
layoutStyle: {
margin: { top: 4 }
},
verticalAlign: ImageSpanAlignment.CENTER
})));
// 添加自定义绘制的标签
let width = 15 + 40 * span.content.length;
if (this.systemLanguage !== 'zh-Hans') {
width = 25 + 21 * span.content.length;
}
this.styledStrings.push(new MutableStyledString(new MyDrawCustomSpan(
span.content, width, 20, this.systemLanguage, span.url, gUIContext
)));
}自定义绘制组件 MyDrawCustomSpan:
export class MyDrawCustomSpan extends CustomSpan {
onMeasure(): CustomSpanMetrics {
let _width = 5 + this.word.length * 13;
if (this.systemLanguage !== 'zh-Hans') {
_width = 10 + this.word.length * 7;
}
return { width: _width, height: this.height };
}
onDraw(context: DrawContext, options: CustomSpanDrawInfo) {
let canvas = context.canvas;
const brush = new drawing.Brush();
// 设置画笔颜色
if (this.color === 'green') {
brush.setColor({ alpha: 51, red: 100, green: 187, blue: 92 });
} else if (this.color === 'red') {
brush.setColor({ alpha: 51, red: 187, green: 100, blue: 92 });
}
canvas.attachBrush(brush);
// 绘制圆角矩形背景
let rect: common2D.Rect = {
left: options.x - 50,
top: options.lineTop + 11,
right: options.x + this.width,
bottom: options.lineBottom
};
let roundRect = new drawing.RoundRect(rect, 10, 10);
canvas.drawRoundRect(roundRect);
// 绘制文本
const font = new drawing.Font();
font.setSize(40);
const textBlob = drawing.TextBlob.makeFromString(
this.word, font, drawing.TextEncoding.TEXT_ENCODING_UTF8
);
canvas.attachBrush(brush);
canvas.drawTextBlob(textBlob, options.x + 5, options.lineBottom - 10);
canvas.detachBrush();
}
}关键点:
- 继承
CustomSpan实现完全自定义绘制 onMeasure()方法计算所需宽度onDraw()方法使用drawingAPI 绘制圆角矩形和文本- 支持中英文自适应宽度计算
页面导航架构
项目使用 Navigation 组件和 NavPathStack 实现页面导航:
@Entry
@Component
struct Home {
@Provide('NavPathStack') pageInfos: NavPathStack = new NavPathStack()
@Builder
PagesMap(name: string) {
if (name === 'TitleLink') {
TitleLink();
} else if (name === 'CustomizeEmoji') {
CustomizeEmoji();
} else if (name === 'VideoLink') {
VideoLink();
} else if (name === 'ImageText') {
ImageText();
}
}
build() {
Navigation(this.pageInfos) {
// 主页内容...
}
.mode(NavigationMode.Stack)
.hideTitleBar(true)
.navDestination(this.PagesMap)
}
}导航跳转:
// 从主页跳转到子页面
this.pageInfos.pushPathByName('TitleLink', '');
// 在子页面中返回
let router = this.getUIContext().getRouter();
router.pushUrl({ url: span.url });国际化支持
项目支持中英文双语,通过 i18n.System.getSystemLanguage() 获取系统语言,并加载对应的 mock 数据:
aboutToAppear(): void {
this.systemLanguage = i18n.System.getSystemLanguage();
this.handleStyledString();
}
handleStyledString() {
if (this.systemLanguage === 'zh-Hans') {
this.spans = TitleLinkMock;
} else {
this.spans = TitleLinkMock_EN;
}
// 处理文本...
}运行效果
首页
应用启动后显示主页,提供四个功能入口按钮:

高亮超链接文本
点击"高亮显示的超链接文本"按钮,进入高亮超链接页面。文本中的 #话题 和 @用户 以蓝色高亮显示,支持点击跳转:

自定义图片表情
点击"自定义图片表情"按钮,进入表情页面。文本中嵌入了自定义表情图片,与文字混合显示:

图标与文本组合
点击"图标与文本的组合元素"按钮,进入视频链接页面。每个视频链接前添加了播放图标,支持点击跳转:

自定义图文元素
点击"自定义的图文元素"按钮,进入图文元素页面。使用 CustomSpan 实现了带有圆角矩形背景的文本标签:

总结
本示例全面展示了 HarmonyOS ArkUI 中富文本显示的多种实现方案:
- MutableStyledString:通过属性字符串实现文本样式定制和手势交互
- ImageAttachment:在文本中嵌入图片资源
- CustomSpan:实现完全自定义的文本绘制逻辑
- TextController:统一管理富文本内容的显示
这些方案可以灵活组合,满足各种复杂的富文本显示需求,如社交媒体动态、新闻详情、聊天消息等场景。项目代码结构清晰,注释完善,是学习 HarmonyOS 富文本开发的优秀参考。
适用场景:
- 社交媒体应用中的话题、用户提及
- 新闻资讯应用中的图文混排
- 聊天应用中的表情和自定义消息
- 任何需要在文本中嵌入可交互元素的场景
学习建议:
- 从
MyCustomSpan数据模型开始,理解富文本片段的抽象方式 - 学习
MutableStyledString的属性设置,掌握样式定制方法 - 研究
CustomSpan的自定义绘制,了解底层渲染能力 - 参考
HandleData的合并逻辑,理解富文本内容的组装流程