Text组件尾部省略号后为什么还有一段空白,没有占满组件宽度

问题现象

在Text组件上未设置宽度,当内容过长时,省略号与组件边缘之间会留有较大空白,且内容更新时省略号的位置会发生变化。
在这里插入图片描述

原因分析

当Text组件未设置宽度且内容超长时,组件宽度将采用父组件传递的布局约束的最大宽度。省略开始位置会根据不同的断词模式导致排版塑型结果有所不同,因此不同内容的省略开始位置也会不同。

解决措施

设置wordBreak属性为WordBreak.BREAK_ALL,任意2个字符间断行使文本内容尽量占满组件区域。 示例代码如下:

@Entry
@Component
struct Index {
  @State message: string = '混合Hello World! honorificabilitudinitatibus!';

  build() {
    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组件如何实现行末展开样式

解决措施

自行测算截断字符,并在行末添加…展开或者…图标作为组件内容。

Text组件如何实现不设置maxLines在固定布局约束下内容超出仍显示省略样式

问题现象

在固定尺寸的组件区域内,不同字号的内容显示的最大行数会有所不同。期望实现内容超长时自动显示省略样式,则无需设置固定的maxLines值。

解决措施

设置heightAdaptivePolicy为TextHeightAdaptivePolicy.LAYOUT_CONSTRAINT_FIRST,该模式会删除超过布局约束的行,从而实现类似设置maxLines的效果。 示例代码如下:

@Entry
@Component
struct Index {
  @State message: string = '混合Hello World! 多行文本 中英文数字混合 1282378283 ~';
  @State fontSize: number = 25;

  build() {
    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中的文本不能对齐。此时,可以在层叠布局 (Stack)中放置标签和长文本,给中间多行文本设置首行文本缩进距离textIndent。多行文本后面的标签则需要通过offset属性调整位置。这种实现方式,可以让三个Text组件中的文字水平对齐。 实现步骤:

  1. 将标签和长文本放在Stack中。
  2. 在显示之前的回调aboutToAppear中,使用measureTextSize计算前标签的宽度,作为中间多行文本的首行缩进距离。
  3. 通过getparagraphs计算中间多行文本最后一行的宽度、除最后一行文本之外的高度,作为后标签的偏移量offset。
  4. 设置后标签相对于Stack左上角的偏移量。
import { LengthMetrics } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  @State message: string = '这是一段长文本,超长部分折行,前后添加标签';
  @State frontTag: string = '前标签';
  @State backTag: string = '后标签';
  @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() {
    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来同时展示表情和文字。

@Entry
@Component
struct Index {
  @State fulltext: string =
    '你好我是Text[grin],你好我[rolling_on_the_floor_laughing]是Text,[slightly_smiling_face]你好我是Text[grin]';

  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]) { // $r("...")需要替换为开发者所需的图像资源
        case 'rolling_on_the_floor_laughing':
          emojisImg.push($r("app.media.rolling_on_the_floor_laughing"))
        case 'slightly_smiling_face':
          emojisImg.push($r("app.media.slightly_smiling_face"))
        case 'grin':
          emojisImg.push($r("app.media.grin"))
        default:
      }
    }
    return emojisImg
  }

  build() {
    Column() {
      TextInput({
        placeholder: "用户输入带表情的文本,例如:你好[grin]"
      })
        .width('80%')
        .padding(10)
        .border({ width: 1, color: '#EEEEEE' })
        .onChange((value: string) => {
          // 输入变化时,更新 fulltext
          this.fulltext = value;
        });

      Text() {
        ForEach(TextExample.classifyTextAndEmojis(this.fulltext).get('text'),
          (item: string, index: number) => { // 展示文本和自定义表情资源
            Span(item)
              .fontSize(18)
              .fontColor('#666666')
              .fontWeight(FontWeight.Regular)

            ImageSpan(TextExample.getEmojiImg(
              TextExample.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
struct Index {
  @State message: string = '这是一段超长文本'.repeat(50);

  build() {
    Column() {
      Text(this.message)
        .height('auto')
        .maxLines(3)
    }
    .height(200)
    .width('80%')
    .margin('10%')
    .borderWidth(1)
    .justifyContent(FlexAlign.Center)
  }
}

在这里插入图片描述

解决措施二

解决措施一的缺点是有部分文本被裁剪掉,如果开发者想要全部文本可以被阅读,可以把Text组件放在滚动容器Scroll里面,通过手势滑动来浏览全部文本。

@Entry
@Component
struct Index {
  @State message: string = '这是一段超长文本'.repeat(50);

  build() {
    Column() {
      Scroll() {
        Text(this.message)
      }
      .scrollBar(BarState.Off)
    }
    .height(200)
    .width('80%')
    .margin('10%')
    .borderWidth(1)
    .justifyContent(FlexAlign.Center)
  }
}

在这里插入图片描述

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐