实现文本场景化鸿蒙示例代码

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

介绍

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

实现文本场景化源码链接

效果预览

使用说明

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

实现思路

  1. 多行文本展示一行: 使用measureText(options: MeasureOptions): number接口计算指定文本单行布局下的宽度; 使用getDefaultDisplaySync(): Display获取屏幕宽度; 使用屏幕宽度减去padding等需要减去的宽度获取屏幕可用宽度。
typescript 复制代码
@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;
}
typescript 复制代码
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来实现停滞操作。
typescript 复制代码
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 }
typescript 复制代码
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)
  })
}
  1. 展示全文:用"屏幕宽度 * 最大行数 * 组件宽度比例"的结果和"实际文本宽度"进行比较大小,判断是否需要"...展开全文"; 当需要"...展开全文"时,只展示maxLines属性设置的固定高度的文本内容,当点击"...展开全文"时将该文本改成"...收起"并添加curves.springMotion曲线动画,同时在animateTo的显示动效的闭包函数中将maxLines属性值修改为-1使得该属性无效;当需要"...收起"时,将collapseText的值改为"...展开全文",并设置收起动画。
typescript 复制代码
// 长文本状态(展开 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))
  })
}
typescript 复制代码
.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组件添加图标和超链接功能,在点击事件中显示提示信息或执行跳转视频页操作。
typescript 复制代码
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 })
    }
  })
}
typescript 复制代码
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')
    });
  }
})
typescript 复制代码
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 组件,配合实现文本高亮标记和清除,并且通过键对数据库实现标记数据的持久化。
typescript 复制代码
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接口计算,监听到截断时显示。
typescript 复制代码
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 =  '文本未截断'
    }
  })
相关推荐
红虾程序员12 分钟前
Linux进阶命令
linux·服务器·前端
yinuo13 分钟前
uniapp在微信小程序中实现 SSE 流式响应
前端
lynx_22 分钟前
又一个跨端框架——万字长文解析 ReactLynx 实现原理
前端·javascript·前端框架
子燕若水28 分钟前
UE5 Chaos :官方文献总结 + 渲染网格体 (Render Mesh) 和模拟网格体 是如何关联的?为什么模拟网格体 可以驱动渲染网格体?
前端
Anlici40 分钟前
深度前端面试知识体系总结
前端·面试
夜寒花碎1 小时前
前端基础理论——02
前端·javascript·html
uhakadotcom1 小时前
简单易懂的Storybook介绍:让前端UI组件开发变得更高效
前端·javascript·面试
bnnnnnnnn1 小时前
前端实现多服务器文件 自动同步宝塔定时任务 + 同步工具 + 企业微信告警(实战详解)
前端·javascript·后端
返乡coder1 小时前
一文掌握React基础用法:从零开始构建现代Web应用
前端
DataFunTalk1 小时前
乐信集团副总经理周道钰亲述 :乐信“黎曼”异动归因系统的演进之路
前端·后端·算法