本文原创发布在华为开发者社区

介绍

本示例为文本场景的整合demo,主要包括六个场景示例:多行文本只展示一行、跑马灯案例、展示全文、Text实现部分文本高亮和超链接样式、文本高亮标记功能、文字截断。

实现文本场景化源码链接

效果预览

请添加链接描述

使用说明

点击对应模式按钮即可展示对应功能。

实现思路

  1. 多行文本展示一行: 使用measureText(options: MeasureOptions): number接口计算指定文本单行布局下的宽度;
    使用getDefaultDisplaySync(): Display获取屏幕宽度;
    使用屏幕宽度减去padding等需要减去的宽度获取屏幕可用宽度。

    @State isShowSheet: boolean = false;
    @State message: string = 'Hello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello World'
    @State textWidth : number = measure.measureText({
      textContent: this.message,
      fontSize: 12
    })
    @State getScreenAble: number = 0
    aboutToAppear(){
      let windowWidth = px2vp(display.getDefaultDisplaySync().width);
      this.getScreenAble = windowWidth - 70;
    }
ext(this.message)
  .textOverflow({ overflow: TextOverflow.Ellipsis })
  .maxLines(1)
  .constraintSize({ maxWidth: this.getScreenAble })

  if (this.textWidth >= this.getScreenAble) {
    Image($r('app.media.right'))
      .fillColor(Color.Black)
      .size({width: ('12vp')})
      .bindSheet(this.isShowSheet,this.myDialog(),{
        height: 400,
        dragBar: false,
        onDisappear: () => {
          this.isShowSheet = !this.isShowSheet;
        }
      })
      .onClick(() => {
        this.isShowSheet = !this.isShowSheet;
      })
  }
  1. 跑马灯案例:Text组件外层包裹一层Scroll组件,Scroll组件设置一定的百分比宽度值,并获取当前文本内容宽度和Scroll组件宽度。
    文本宽度大于 Scroll组件宽度时,通过添加判断显示同样的文本,在偏移过程中可实现文本接替并显示在同一显示区的效果。
    页面进来执行文本滚动函数scrollAnimation(),在指定的时间内完成文本的偏移,当循环一次之后,通过定时器setTimeout来实现停滞操作。

    Scroll() {
      Row() {
     Text(this.tripDataItem.ticketEntrance)
       .onAreaChange((oldValue, newValue) => {
         logger.info(`TextArea oldValue:${JSON.stringify(oldValue)},newValue:${JSON.stringify(newValue)}`);
         // 获取当前文本内容宽度
         this.ticketCheckTextWidth = Number(newValue.width);
       })
       if (this.ticketCheckTextWidth >= this.ticketCheckScrollWidth) {
         Blank()
           .width(Constants.BLANK_SPACE)
         Text(this.tripDataItem.ticketEntrance)
       }
     }.offset({ x: this.ticketCheckTextOffset })
      }
      .width(display.isFoldable() ? $r('app.string.marquee_scroll_phone_width') : $r('app.string.marquee_scroll_tablet_width'))
      .id('marquee')
      .alignRules({
     top: { anchor: '__container__', align: VerticalAlign.Top },
     left: { anchor: 'ticketEntrance', align: HorizontalAlign.End }
    scrollAnimation() {
      // 文本宽度小于Scroll组件宽度,不执行滚动操作
      if (this.ticketCheckTextWidth < this.ticketCheckScrollWidth) {
     return;
      }
      animateTo({
     duration: Constants.ANIMATION_DURATION,
     curve: Curve.Linear,
     delay: this.delay,
     onFinish: () => {
       setTimeout(() => {
         // 初始化文本偏移量
         this.ticketCheckTextOffset = 0;
         // 执行动画函数
         this.scrollAnimation();
       }, Constants.DELAY_TIME)
     }
      }, () => {
     // 文本偏离量
     this.ticketCheckTextOffset = -(this.ticketCheckTextWidth + Constants.BLANK_SPACE)
      })
    }
  2. 展示全文:用“屏幕宽度 最大行数 组件宽度比例”的结果和“实际文本宽度”进行比较大小,判断是否需要“...展开全文”; 当需要“...展开全文”时,只展示maxLines属性设置的固定高度的文本内容,当点击“...展开全文”时将该文本改成“...收起”并添加curves.springMotion曲线动画,同时在animateTo的显示动效的闭包函数中将maxLines属性值修改为-1使得该属性无效;当需要“...收起”时,将collapseText的值改为“...展开全文”,并设置收起动画。
// 长文本状态(展开 or 收起)
@State collapseText: string = '...展开全文'
// 屏幕宽度(单位px)
screenWidth: number = 0;
// 是否需要显示"展开"字样(注:当文本长度较短时就不需要“展开”)
@State isExpanded: boolean = false
// 测量文本宽度(单位px)
@State textWidth: number = measure.measureText({
  textContent: this.longMessage,
  fontSize: 15
})
// 获取当前所有的display对象
promise: Promise<Array<display.Display>> = display.getAllDisplays()

aboutToAppear() {
  this.promise.then((data: Array<display.Display>) => {
    hilog.info(0x0000,'所有的屏幕信息:%{public}s',JSON.stringify(data))
    //单位为像素
    this.screenWidth = data[0].width
    // 屏幕宽度 * 最大行数 * 组件宽度比例 和 文字测量宽度
    this.isExpanded = this.screenWidth * this.lines * 0.8 <= this.textWidth
  }).catch((err: BusinessError) => {
    hilog.error(0x0000,'Failed to obtain all the display objects. Code:%{public}s',JSON.stringify(err))
  })
}
.onClick(() => {
  if (this.collapseText === '...展开全文') {
    this.collapseText = '...收起';
    // 展开动画
    animateTo({
      duration: 150,
      curve: curves.springMotion(0.5, 0.8),
    }, () => {
      this.lines = -1; // 使得设置的最大行属性无效
    })
  } else {
    this.collapseText = '...展开全文';
    // 收起动画
    animateTo({
      duration: 100,
      curve: Curve.Friction,
    }, () => {
      this.lines = 3; // 只显示3行
     })
  }
})
  1. Text实现部分文本高亮和超链接样式:使用Text组件结合ForEach方法遍历spans中的CustomSpan对象,根据不同的Span类型生成不同样式和功能的Span组件;对于Hashtag、Mention和DetailLink类型的Span,在TextLinkSpan组件中添加带有超链接功能的Span组件,根据CustomSpan的类型和内容,实现对应的样式和交互功能,例如显示提示信息或执行其他操作;对于VideoLink类型的Span,使用VideoLinkSpan组件添加图标和超链接功能,在点击事件中显示提示信息或执行跳转视频页操作。
Text() {
  ForEach(this.spans, (item: CustomSpan) => {
    if (item.type === CustomSpanType.Normal) {
      Span(item.content)
        .fontSize($r('app.string.ohos_id_text_size_body1'))
    } else if (item.type === CustomSpanType.Hashtag ||
      item.type === CustomSpanType.Mention ||
      item.type === CustomSpanType.DetailLink) {
      TextLinkSpan({ item: item })
    } else {
      VideoLinkSpan({ item: item })
    }
  })
}
Span(this.myItem.content)
  .fontColor($r('app.color.styled_text_link_font_color'))// 超链接字体颜色
  .fontSize($r('app.string.ohos_id_text_size_body1'))
  .textBackgroundStyle({ color: this.linkBackgroundColor })
  .onClick(() => {
    this.linkBackgroundColor = $r('app.color.styled_text_link_clicked_background_color'); // 点击后的背景色
    setTimeout(() => {
      this.linkBackgroundColor = Color.Transparent;
    }, BACKGROUND_CHANGE_DELAY)
  // 根据文本超链接的类型做相应处理
    if (this.myItem.type === CustomSpanType.Hashtag) {
      promptAction.showToast({
      message: $r('app.string.styled_text_hashtag_toast_message')
    });
    } else if (this.myItem.type === CustomSpanType.Mention) {
      promptAction.showToast({
      message: $r('app.string.styled_text_user_page_toast_message')
    });
    } else {
      promptAction.showToast({
      message: $r('app.string.styled_text_content_details_toast_message')
    });
  }
})
ContainerSpan() {
  ImageSpan($r('app.media.styled_text_ic_public_video'))
    .height($r('app.integer.styled_text_video_link_icon_height'))
    .verticalAlign(ImageSpanAlignment.CENTER)
  Span(this.myItem.content)
    .fontColor($r('app.color.styled_text_link_font_color'))
    .fontSize($r('app.string.ohos_id_text_size_body1'))
    .onClick(() => {
      this.linkBackgroundColor = $r('app.color.styled_text_link_clicked_background_color');
      setTimeout(() => {
        this.linkBackgroundColor = Color.Transparent;
      }, BACKGROUND_CHANGE_DELAY)
      promptAction.showToast({
        message: $r('app.string.styled_text_video_function_message')
      });
    })
}
.textBackgroundStyle({ color: this.linkBackgroundColor })
  1. 文本高亮标记功能:通过 Text 和 Span 组件,配合实现文本高亮标记和清除,并且通过键对数据库实现标记数据的持久化。
markHighlight(highlight: boolean) {
  if (this.selectionStart !== -1 && this.selectionEnd !== -1) {
    let leftMarks: HighlightMark[] = []; // 区域左侧的文本样式数据
    let rightMarks: HighlightMark[] = []; // 区域右侧的文本样式数据
    // 当前需要更改样式的区域
    let currentSelection: HighlightMark = {
      start: this.selectionStart,
      end: this.selectionEnd,
      color: highlight ? this.colorList[this.currentColor] : Color.Transparent
    }
    this.markList.forEach((mark) => {
      // 如果既有样式区间开头在当前选中区域开头之前
      if (mark.start < currentSelection.start) {
        // 如果既有样式区间结尾在当前选中区域开头之前,即两个区间无交集
        if (mark.end < currentSelection.start) {
          leftMarks.push({
            start: mark.start,
            end: mark.end,
            color: mark.color
          });
        } else if (mark.color === currentSelection.color) {
          // 两个区间有交集且样式一致,合并入当前选中区域
          currentSelection.start = mark.start;
        } else {
          // 样式不一致,截断区间
          leftMarks.push({
            start: mark.start,
            end: currentSelection.start,
            color: mark.color
          });
        }
      }
      // 既有样式区间在当前选中区域末尾之后
      if (mark.end > currentSelection.end) {
        // 如果既有样式区间开头在当前选中区域开头之后,即两个区间无交集
        if (mark.start > currentSelection.end) {
          rightMarks.push({
            start: mark.start,
            end: mark.end,
            color: mark.color
          });
        } else if (mark.color === currentSelection.color) {
          // 两个区间有交集且样式一致,合并入当前选中区域
          currentSelection.end = mark.end;
        } else {
          // 样式不一致,截断区间
          rightMarks.push({
            start: currentSelection.end,
            end: mark.end,
            color: mark.color
          });
        }
      }
    });
    // 合并当前与左右文本样式数据
    this.markList = leftMarks.concat(currentSelection, rightMarks)
    LOGGER.info(JSON.stringify(this.markList));
  } 
}
  1. 展示全文:使用measureText(options: MeasureOptions): number接口计算,监听到截断时显示。
TextInput({ text: this.text, placeholder: 'input your word...', controller: this.controller })
  .placeholderColor(Color.Grey)
  .placeholderFont({ size: 14, weight: 400 })
  .caretColor(Color.Blue)
  .width(400)
  .height(40)
  .margin(20)
  .fontSize(14)
  .fontColor(Color.Black)
  .onChange((value: string) => {
    this.text = value
    let textSizeShow1 : SizeOptions = measure.measureTextSize({
      textContent: this.text,
      constraintWidth: 100,
      fontSize: 14,
      overflow: TextOverflow.Ellipsis,
      maxLines: 2
    })
    let textSizeShow2 : SizeOptions = measure.measureTextSize({
      textContent: this.text + ' ',
      constraintWidth: 100,
      fontSize: 14,
      overflow: TextOverflow.Ellipsis,
      maxLines: 2000000
    })
    if (textSizeShow2 &&
      textSizeShow1 &&
      textSizeShow2?.height &&
      textSizeShow1?.height &&
      (textSizeShow2?.height > textSizeShow1?.height)) {
      this.truncatedHint =  '文本截断'
    } else {
      this.truncatedHint =  '文本未截断'
    }
  })

鸿蒙场景化代码
1 声望0 粉丝