使用文本常见问题
本文档介绍使用文本的常见问题并提供参考。
文本显示(Text/Span)常见问题
以下内容介绍了使用Text和Span组件进行文本显示时可能遇到的常见问题,包括文本截断、添加标签、显示表情等场景的处理方法。
Text组件尾部省略号后为什么还有一段空白,没有占满组件宽度
问题现象
在Text组件上未设置宽度,当内容过长时,省略号与组件边缘之间会留有较大空白,且内容更新时省略号的位置会发生变化。
原因分析
当Text组件未设置宽度且内容超长时,组件宽度将采用父组件传递的布局约束的最大宽度。不同内容、不同的断词模式导致排版塑型结果不同,因此省略开始位置也会不同。
解决措施
设置wordBreak属性为WordBreak.BREAK_ALL,任意2个字符间断行使文本内容尽量占满组件区域。
示例代码如下:
import { common } from '@kit.AbilityKit';
@Entry
@Component
export struct WordBreakd {
private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
private manager = this.context.resourceManager;
// 'Text_WordBreak'资源文件中的value值为'混合Hello World! honorificabilitudinitatibus'
@State message: string = this.manager.getStringByNameSync('Text_WordBreak');
build() {
NavDestination() {
Column() {
Text(this.message)
.id('HelloWorld')
.fontSize('25fp')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis})
.onClick(() => {
this.message = 'Welcome try try try 1235628327434348';
})
.border({ width: 1})
.wordBreak(WordBreak.BREAK_ALL) // 修改断词模式
}
.width(300)
.border({ width: 1, color: Color.Blue})
.margin({left: 30, top: 50})
}
// ...
}
}

Text组件如何实现行末展开样式
解决措施
自行测算截断字符,并在行末添加...展开或者...图标作为组件内容。实现方式请参考属性字符串转Paragraph数组、文本展开折叠。
Text组件如何实现内容超长时自动显示省略样式吗?
问题现象
在固定尺寸的组件区域内,不同字号的内容显示的最大行数会有所不同。期望实现内容超长时自动显示省略样式,则无需设置固定的maxLines值。
解决措施
设置heightAdaptivePolicy为TextHeightAdaptivePolicy.LAYOUT_CONSTRAINT_FIRST,该模式会删除超过布局约束的行,从而实现类似设置maxLines的效果。
示例代码如下:
import { common } from '@kit.AbilityKit';
@Entry
@Component
export struct HeightAdaptivePolicy {
private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
private manager = this.context.resourceManager;
// 'Text_Adaptive_Layout'资源文件中的value值为'混合Hello World! 多行文本 中英文数字混合 1282378283 ~'
@State message: string = this.manager.getStringByNameSync('Text_Adaptive_Layout');
@State fontSize: number = 25;
build() {
NavDestination() {
Column({ space: 10 }) {
Text(this.message)
.id('HelloWorld')
.fontSize(this.fontSize)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.border({ width: 1 })
.heightAdaptivePolicy(TextHeightAdaptivePolicy.LAYOUT_CONSTRAINT_FIRST) // 调整自适应布局策略
.width(300)
.height(200)
Row() {
Button('fontSize+5')
.onClick(() => {
this.fontSize += 5;
})
Button('fontSize-5')
.onClick(() => {
this.fontSize -= 5;
})
}
}
.margin({ left: 30, top: 50 })
}
// ...
}
}

在文本前后添加自定义标签
问题现象
如何在文本的前后各添加一个标签,例如“专题”或“Top1”,且这些标签的背景设置、尺寸设置需要能够自定义。
解决措施一
如果标签和中间的长文本需在同一行显示,开发者可能会考虑使用Span实现,但是Span不支持设置尺寸。此时,可以在弹性布局 (Flex)或者Row中放置标签和长文本,并为长文本设置textOverflow属性,以确保文本超长时能够自适应截断,显示在一行之内。
实现步骤:
1.将标签和长文本放在同一个沿水平方向布局的容器Row中。
2.中间长文本设置textOverflow属性为TextOverflow.Ellipsis,空间不足时截断文本,显示省略号。
实现案例请参考实现热搜榜,该示例中,文字“1”、“爆”就是“我是热搜词条”的两个标签。这种实现方式写法简便,适合单行文本添加标签的场景。
解决措施二
如果需在多行文本前后添加标签并且不截断文本,上述方案会导致三个Text中的文本不能对齐,因为多行文本会在Row的宽度内折行。此时,可以在层叠布局 (Stack)中放置标签和长文本,给中间多行文本设置首行文本缩进距离textIndent。多行文本后面的标签则需要通过offset属性调整位置。这种实现方式,可以让三个Text组件中的文字水平对齐。实现步骤如下:
实现步骤:
1.将标签和长文本放在Stack中。
2.在组件显示之前的回调aboutToAppear中,使用measureTextSize计算前标签的宽度,作为中间多行文本的首行缩进距离。
3.在组件显示之前的回调aboutToAppear中,通过getParagraphs计算中间多行文本最后一行的宽度、除最后一行文本之外的高度,作为后标签的偏移量offset。
4.设置后标签相对于Stack左上角的偏移量。
示例:
import { LengthMetrics } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
@Entry
@Component
export struct LengthMetric {
private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
private manager = this.context.resourceManager;
// 'Text_Add_Tags_Front_and_Post'资源文件中的value值为'这是一段长文本,超长部分折行,前后添加标签'
@State message: string = this.manager.getStringByNameSync('Text_Add_Tags_Front_and_Post');
// 'Text_Add_Tags_Front'前标签'
@State frontTag: string = this.manager.getStringByNameSync('Text_Add_Tags_Front');
// 'Text_Add_Tags_Post'资源文件中的value值为'后标签'
@State backTag: string = this.manager.getStringByNameSync('Text_Add_Tags_Post');
@State frontPaddingVp: number = 20;
@State backPaddingVp: number = 10;
@State fontTagWidthVp: Length = 0;
@State backTagWidthVp: Length = 0;
@State backOffsetVpX: Length = 0;
@State backOffsetVpY: Length = 0;
@State messageLines: number = 0;
@State stackWidthVp: number = 300;
// 显示之前,测算前后标签的位置,中间文本的缩进距离
aboutToAppear(): void {
// 计算前标签的宽度fontTagWidthVp,作为message的首行缩进距离
let frontTagSize: SizeOptions = this.getUIContext().getMeasureUtils().measureTextSize({
textContent: this.frontTag,
});
this.fontTagWidthVp = this.getUIContext().px2vp(Number(frontTagSize.width)) + this.frontPaddingVp * 2
// 计算frontTag+message占据的行数
let linesFrontTagPlusMessage = 0;
let mutableStr = new MutableStyledString(this.message,
[{
start: 0,
length: 1,
styledKey: StyledStringKey.PARAGRAPH_STYLE,
styledValue: new ParagraphStyle({ textIndent: LengthMetrics.vp(this.fontTagWidthVp) })
}]
)
let paragraphArr = this.getUIContext()
.getMeasureUtils()
.getParagraphs(mutableStr, { constraintWidth: LengthMetrics.vp(this.stackWidthVp) });
for (let i = 0; i < paragraphArr.length; ++i) {
linesFrontTagPlusMessage += paragraphArr[i].getLineCount();
}
// 后标签offsetX的偏移量backOffsetVpX=frontTag+message最后一行的宽度
this.backOffsetVpX =
this.getUIContext().px2vp((paragraphArr[paragraphArr.length-1].getLineWidth(linesFrontTagPlusMessage - 1)))
// 后标签offsetY的偏移量backOffsetVpY=frontTag+message总高度-最后一行的高度
let heightFrontTagPlusMessageVp = 0;
for (let i = 0; i < paragraphArr.length; ++i) {
heightFrontTagPlusMessageVp += this.getUIContext().px2vp(paragraphArr[i].getHeight());
}
let lastLineHeight =
this.getUIContext().px2vp(paragraphArr[paragraphArr.length-1].getLineHeight(linesFrontTagPlusMessage - 1))
this.backOffsetVpY = heightFrontTagPlusMessageVp - lastLineHeight
}
build() {
NavDestination() {
Column({ space: 20 }) {
Blank()
.height(200)
Stack() {
Text(this.frontTag)
.padding({ left: this.frontPaddingVp, right: this.frontPaddingVp })
.backgroundColor('rgb(39, 135, 217)')
Text(this.message)
.textIndent(this.fontTagWidthVp)
.padding(0)
Text(this.backTag)
.padding({ left: this.backPaddingVp, right: this.backPaddingVp })
.backgroundColor('rgb(0, 74, 175)')
.offset({
x: this.backOffsetVpX,
y: this.backOffsetVpY
})
}
.alignContent(Alignment.TopStart) // 顶部起始端对齐
.width(this.stackWidthVp)
}
.height('100%')
.width('90%')
.padding('5%')
}
// ...
}
}
![]()
Text组件如何实现表情与文字一起显示
问题现象
emoji表情有时以表情符号的形式表示,如何将表情符号转换为emoji表情,并在Text组件中与文字一同显示?
解决措施
使用正则表达式解析表情符号,再将表情符号与图片资源建立映射,通过Span和ImageSpan同时展示表情和文字。
// 请将$r('app.media.xxx')替换为实际资源文件
import { common } from '@kit.AbilityKit';
@Entry
@Component
export struct DisplayedTogether {
private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
private manager = this.context.resourceManager;
// 'Text_Full_Text'资源文件中的value值为
// '你好我是Text[grin],你好我[rolling_on_the_floor_laughing]是Text,[slightly_smiling_face]你好我是Text[grin]'
@State fulltext: string = this.manager.getStringByNameSync('Text_Full_Text');
static classifyTextAndEmojis(input: string): Map<string, string[]> {
const emojiRegex = /\[([a-zA-Z_]+)\]/g; // 根据实际情况编写正则表达式
const resultMap = new Map<string, string[]>(); // 用map记录普通文本和表情
resultMap.set('text', []);
resultMap.set('emojis', []);
let lastIndex = 0;
let match: RegExpExecArray | null = null;
while ((match = emojiRegex.exec(input)) !== null) {
// 添加普通文本
if (match.index >= lastIndex) {
resultMap.get('text')?.push(input.substring(lastIndex, match.index));
}
// 添加匹配到的表情
resultMap.get('emojis')?.push(match[1]);
lastIndex = match.index + match[0].length;
}
// 添加最后一段文本
if (lastIndex < input.length) {
resultMap.get('text')?.push(input.substring(lastIndex));
}
return resultMap;
}
static getEmojiImg(emojis: string[]): Resource[] { // 根据正则匹配结果返回自定义表情资源
let emojisImg: Resource[] = []
for (let i = 0; i < emojis.length; i++) {
switch (emojis[i]) {
case 'rolling_on_the_floor_laughing':
emojisImg.push($r('app.media.rolling_on_the_floor_laughing'))
break;
case 'slightly_smiling_face':
emojisImg.push($r('app.media.slightly_smiling_face'))
break;
case 'grin':
emojisImg.push($r('app.media.grin'))
break;
default:
break;
}
}
return emojisImg
}
build() {
NavDestination() {
Column() {
TextInput({
// 请将$r('app.string.Text_emoji')替换为实际资源文件,在本示例中该资源文件的value值为"用户输入带表情的文本,例如:你好[grin]"
placeholder: $r('app.string.Text_emoji')
})
.width('80%')
.padding(10)
.border({ width: 1, color: '#EEEEEE' })
.onChange((value: string) => {
// 输入变化时,更新 fulltext
this.fulltext = value;
});
Text() {
ForEach(DisplayedTogether.classifyTextAndEmojis(this.fulltext).get('text'),
(item: string, index: number) => { // 展示文本和自定义表情资源
Span(item)
.fontSize(18)
.fontColor('#666666')
.fontWeight(FontWeight.Regular)
ImageSpan(DisplayedTogether.getEmojiImg(
DisplayedTogether.classifyTextAndEmojis(this.fulltext).get('emojis'))[index])
.verticalAlign(ImageSpanAlignment.BOTTOM)
.height(24)
})
}
.width('80%')
.padding(15)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.padding(20)
}
// ...
}
}

文本超长时如何展示
问题现象
Text组件中内容过多,超出父组件容器Column的高度,会导致文本溢出容器边界,如何让文本显示在父组件容器的区域内?
解决措施一
Text文本是自动折行的,当没有限制Text高度height时,Text高度在文本的行数增加时自动调整。可以通过设置maxLines属性限制文本的最大行数,如果有多余的文本默认会被截断。也可以通过textOverflow属性来指定截断方式。
以下示例展示了限制Text组件不超过三行的场景。
@Entry
@Component
export struct TextLong {
private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
private manager = this.context.resourceManager;
// 'Text_Long_String'资源文件中的value值为'这是一段超长文本'
@State message: string = this.manager.getStringByNameSync('Text_Long_String').repeat(50);
build() {
NavDestination() {
Column() {
Text(this.message)
.height('auto')
.maxLines(3)
}
.height(200)
.width('80%')
.margin('10%')
.borderWidth(1)
.justifyContent(FlexAlign.Center)
}
// ...
}
}

解决措施二
上述方法会导致部分文本被裁剪掉,如果需要保留全部文本,可以把Text组件放在滚动容器Scroll内,再通过手势滑动来浏览全部文本,具体示例如下:
@Entry
@Component
export struct TextLongTow {
private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
private manager = this.context.resourceManager;
// 'Text_Long_String'资源文件中的value值为'这是一段超长文本'
@State message: string = this.manager.getStringByNameSync('Text_Long_String').repeat(50);
build() {
NavDestination() {
Column() {
Scroll() {
Text(this.message)
}
.scrollBar(BarState.Off)
}
.height(200)
.width('80%')
.margin('10%')
.borderWidth(1)
.justifyContent(FlexAlign.Center)
}
// ...
}
}

selection如何触发弹出自定义菜单并设置菜单字体大小
问题现象
在bindSelectionMenu自定义选择菜单中,可通过TextResponseType设置文本选择菜单的响应类型。通过selection如何触发弹出自定义菜单并设置菜单字体大小。
解决措施
若希望由selection触发自定义菜单,可将TextResponseType设置为DEFAULT。同时,在Menu组件上通过配置font属性,即可自定义菜单的字体大小,灵活适配界面设计需求。
// xxx.ets
@Entry
@Component
struct TextExample8 {
controller: TextController = new TextController();
options: TextOptions = { controller: this.controller };
@State selectStart: number = 0;
@State selectEnd: number = 0;
build() {
Column() {
Column() {
Text("TextTextTextText")
.fontSize(14)
.selection(this.selectStart, this.selectEnd)
.copyOption(CopyOptions.InApp)
.bindSelectionMenu(TextSpanType.TEXT, this.CustomMenu, TextResponseType.DEFAULT, {
onDisappear: () => {
this.selectStart = -1;
this.selectEnd = -1;
},
})
.textAlign(TextAlign.Center)
.borderWidth(1)
.borderColor(Color.Red)
Button("Set selection")
.onClick(() => {
this.selectStart = 0;
this.selectEnd = 10;
})
.fontSize(14)
.margin({ top: 20 })
}
.width('100%')
.padding({ top: 300 })
}
.height('100%')
}
@Builder
CustomMenu() {
Column() {
Menu() {
MenuItem({ content: "Item Content" })
MenuItem({ content: "Item Content" })
MenuItem({ content: "Item Content" })
}
.font({ size: 14 })
.radius($r('sys.float.ohos_id_corner_radius_card'))
.clip(true)
.backgroundColor('#F0F0F0')
}
}
}

如何屏蔽文本的长按手势
问题现象
配置CopyOptions将文本设置为可选择,此时长按文本会选择文字内容并弹出系统菜单,如何使长按手势不生效。
解决措施
想要使长按手势对文本不生效,可以设置触发时间小于系统菜单触发时间(500ms)的自定义长按手势。
// xxx.ets
@Entry
@Component
struct TextExample8 {
build() {
Column() {
Text("TextTextTextText")
.copyOption(CopyOptions.InApp)
.gesture(LongPressGesture({ repeat: false, duration: 400 })
.onAction(() => {
}))
.margin({
top: 100,
bottom: 100,
left: 100,
right: 100
})
}
.height('100%')
}
}
设置enableVariableFontWeight为true后字重不能跟随设置调节
问题现象
Text组件调用fontWeight接口,FontSettingOptions类型的入参options设置enableVariableFontWeight值为true之后,在系统设置的“字体大小和界面缩放”页面中调节字体粗细,Text组件的字重不跟随变化。
解决措施
- 如果需要Text组件字重跟随系统设置变化,需要在configuration标签设置followSystem。
- 字体配置项options控制是否启用可变字重调节。
- 当options的参数enableVariableFontWeight取值true时,启用可变字重调节。weight取值为[100, 900]范围内任意整数时,字重取值为weight。此时如果在设置中调节字体粗细,字重始终是开发者设置的数值weight,字体粗细不会变化。
- 当options的参数enableVariableFontWeight取值false时,禁用可变字重调节。weight取值为[100, 900]范围内的整百数值时,字重取值为weight。weight是非整百数值时,字重取默认值400。此时更改设置中的字体粗细,字重会随设置变化。此时如果在设置中调节字体粗细,字重会随设置变化。
文本输入(TextInput/TextArea/Search)常见问题
以下内容介绍了使用TextInput、TextArea和Search组件输入文本时可能遇到的问题。
TextInput被遮挡时光标仍然不消失
问题现象
在Stack中堆叠了多个组件,包含一个输入框(TextInput),当TextInput组件被遮挡时,偶尔会出现带有小圆圈的手柄,显示在其他组件上。
解决措施
当TextInput组件处于选中状态并显示操作手柄时,选中区域和操作手柄可能不在同一图层渲染。其中,选中区域与输入框在同一图层,而操作手柄则在更高的图层上。因此,当输入框被其他元素遮挡时,选中区域也会被遮挡,但操作手柄仍然可见。
TextInput被遮挡时,如果通过TextInputController设置焦点和选中区域会出现上述现象。涉及的选中区域设置接口包括selectAll和setTextSelection。建议检查应用代码中以下接口的调用时机,在输入框被遮挡后避免设置选中区域。
| 组件 | 接口 |
|---|---|
| Search | setTextSelection |
| TextArea | setTextSelection |
| TextInput | selectAll |
| TextInput | setTextSelection |
以下示例展示了一个典型的问题场景,存在一个内容为“TextInput被遮挡不显示”的TextInput组件被隐藏,但点击按钮后,图片上会出现TextInput操作手柄。此时,开发者需要检查应用代码,确保在输入框被遮挡时没有设置选中区域。移除设置输入框选中区域的代码逻辑,即可解决操作手柄出现的问题。
@Entry
@Component
export struct CursorPersistsWhenTextInputIsCoveredExample {
controller: TextInputController = new TextInputController();
@State message1: string = 'TextInput被遮挡不显示';
build() {
NavDestination() {
Column({ space: 50 }) {
Stack() {
TextInput({ text: this.message1, controller: this.controller })
.copyOption(CopyOptions.LocalDevice)
.backgroundColor(Color.Green)
.width(200)
.id('textInput_1')
// $r('app.media.foreground')需要替换为开发者所需的图像资源文件。
Image($r('app.media.foreground'))
.width(200)
.height(200)
.backgroundColor('rgb(213,213,213)')
}
Button('点击出现手柄')
.onClick(() => {
this.getUIContext().getFocusController().requestFocus('textInput_1')
this.controller.setTextSelection(0, 5, { menuPolicy: MenuPolicy.HIDE })
})
}
.padding('10%')
.alignItems(HorizontalAlign.Center)
.height('100%')
.width('90%')
}
.backgroundColor('#f1f2f3')
.title($r('app.string.Cursor_Persists_When_TextInput_Is_Covered'))
}
}
