好久没写博客啦,延续前几期的风格,小鱼用一个“带便利贴功能的记事本”来比喻Text/Span两个组件。


比喻:带便利贴的记事本

想象你要写一份文档:

  • Text组件 = 一页完整的记事本纸张
  • Span组件 = 贴在纸上的彩色便利贴

一、 Text 组件:记事本纸张

  • 基本文本与资源引用:Text('...')Text($r('app.string.xxx'))
  • 视觉样式:
    • 字体:fontSizefontColorfontWeightfontFamily
    • 盒模型:paddingmarginwidthheight
    • 边框:borderborderWidthborderRadius

示例:

  Text('我是一段文本')
  Text($r('app.string.text1'))
  
  Text($r('app.string.text1'))
          .baselineOffset(0)
          .fontSize(30)
          .border({ width: 1 })
          .borderRadius(10)
          .padding(10)
          .width(300)

二、Span 组件:贴在纸上的彩色便利贴

  • Text(){ Span() } 组合富文本
  • Span 独立样式:字号、颜色、斜体、装饰线
  • Span 点击事件示例:点击触发状态更新
    Text($r('app.string.text2')) {
          Span($r('app.string.text2span'))
        }
        .padding(10)
        .borderWidth(1)


        Text() {
          Span($r('app.string.text3span1'))
            .fontSize(16)
            .fontColor(Color.Grey)
            .decoration({ type: TextDecorationType.LineThrough, color: Color.Red })
          Span($r('app.string.text3span2'))
            .fontColor(Color.Blue)
            .fontSize(16)
            .fontStyle(FontStyle.Italic)
            .decoration({ type: TextDecorationType.Underline, color: Color.Black })
          Span($r('app.string.text3span3'))
            .fontSize(16)
            .fontColor(Color.Grey)
            .decoration({ type: TextDecorationType.Overline, color: Color.Green })

        }
        .borderWidth(1)
        .padding(10)

  Text() {
          Span('I am Upper-span')
            .textCase(TextCase.UpperCase)
            .fontSize(30)
            .onClick(() => {
              this.textStr1 = 'Span onClick is triggering';
            })

        }

        Text('onClick:' + this.textStr1)
          .fontSize(20)

三、对齐与排版控制

  • 水平对齐:textAlign(Start/Center/End)
  • 垂直对齐:textVerticalAlign
  • 基线偏移:baselineOffset
  • 字符间距:letterSpacing
  • 行高:lineHeight
  • 行间距(API 20):lineSpacing(LengthMetrics.px(...))
 Text('左对齐')
          .width(300)
          .textAlign(TextAlign.Start)
          .border({ width: 1 })
          .padding(10)
        Text('中间对齐')
          .width(300)
          .textAlign(TextAlign.Center)
          .border({ width: 1 })
          .padding(10)
        Text('右对齐')
          .width(300)
          .textAlign(TextAlign.End)
          .border({ width: 1 })
          .padding(10)
Text('This is the text with the line height set. This is the text with the line height set.')
          .width(300).fontSize(12).border({ width: 1 }).padding(10)
        Text('This is the text with the line height set. This is the text with the line height set.')
          .width(300)
          .fontSize(12)
          .border({ width: 1 })
          .padding(10)
          .lineHeight(20)


        Text('decoration属性设置文本装饰线样式、颜色及其粗细').fontSize(20).fontColor(Color.Green).fontWeight(700)
        Text('This is the text:LineThrough')
          .decoration({
            type: TextDecorationType.LineThrough,
            color: Color.Red
          })
          .borderWidth(1).padding(15).margin(5)
        Text('This is the text:Overline')
          .decoration({
            type: TextDecorationType.Overline,
            color: Color.Red
          })
          .borderWidth(1).padding(15).margin(5)
        Text('This is the text:Underline')
          .decoration({
            type: TextDecorationType.Underline,
            color: Color.Red
          })
          .borderWidth(1).padding(15).margin(5)
        Text('This is the text:Underline/DASHED')
          .decoration({
            type: TextDecorationType.Underline,
            color: Color.Blue,
            style: TextDecorationStyle.DASHED
          })
          .borderWidth(1).padding(15).margin(5)
        Text('This is the text:Underline/DOTTED')
          .decoration({
            type: TextDecorationType.Underline,
            color: Color.Blue,
            style: TextDecorationStyle.DOTTED
          })
          .borderWidth(1).padding(15).margin(5)
        Text('This is the text:Underline/DOUBLE')
          .decoration({
            type: TextDecorationType.Underline,
            color: Color.Blue,
            style: TextDecorationStyle.DOUBLE
          })
          .borderWidth(1).padding(15).margin(5)
        Text('This is the text:Underline/WAVY')
          .decoration({
            type: TextDecorationType.Underline,
            color: Color.Blue,
            style: TextDecorationStyle.WAVY,
            thicknessScale: 4
          })
          .borderWidth(1).padding(15).margin(5)


        Text('baselineOffset属性设置文本基线的偏移量').fontSize(20).fontColor(Color.Green).fontWeight(700)
        Text('This is the text content with baselineOffset 0.')
          .baselineOffset(0)
          .fontSize(12)
          .border({ width: 1 })
          .padding(10)
          .width('100%')
          .margin(5)
        Text('This is the text content with baselineOffset 30.')
          .baselineOffset(30)
          .fontSize(12)
          .border({ width: 1 })
          .padding(10)
          .width('100%')
          .margin(5)
        Text('This is the text content with baselineOffset -20.')
          .baselineOffset(-20)
          .fontSize(12)
          .border({ width: 1 })
          .padding(10)
          .width('100%')
          .margin(5)


        Text('letterSpacing属性设置文本字符间距').fontSize(20).fontColor(Color.Green).fontWeight(700)
        Text('This is the text content with letterSpacing 0.')
          .letterSpacing(0)
          .fontSize(12)
          .border({ width: 1 })
          .padding(10)
          .width('100%')
          .margin(5)
        Text('This is the text content with letterSpacing 3.')
          .letterSpacing(3)
          .fontSize(12)
          .border({ width: 1 })
          .padding(10)
          .width('100%')
          .margin(5)
        Text('This is the text content with letterSpacing -1.')
          .letterSpacing(-1)
          .fontSize(12)
          .border({ width: 1 })
          .padding(10)
          .width('100%')
          .margin(5)

四、溢出与省略策略

  • 溢出模式:
    • TextOverflow.None
    • TextOverflow.Ellipsis
    • TextOverflow.MARQUEE
  • 跑马灯配置:marqueeOptions
  • 混合中英数字截断:wordBreak(WordBreak.BREAK_ALL) + ellipsisMode
        Text('textOverflow 属性的使用').fontSize(20).fontColor(Color.Green).fontWeight(700)
        Text('This is the setting of textOverflow to Clip text content This is the setting of textOverflow ' +
          'to None text content. This is the setting of textOverflow to Clip text content This is the setting ' +
          'of textOverflow to None text content.')
          .width(250)
          .textOverflow({ overflow: TextOverflow.None })
          .maxLines(1)
          .fontSize(12)
          .border({ width: 1 })
          .padding(10)

        Text('我是超长文本,超出的部分显示省略号 I am an extra long text, with ellipses displayed for any excess。')
          .width(250)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .maxLines(1)
          .fontSize(12)
          .border({ width: 1 })
          .padding(10)

        Text('当文本溢出其尺寸时,文本将滚动显示 When the text overflows its dimensions,the text will scroll for displaying.')
          .width(250)
          .textOverflow({ overflow: TextOverflow.MARQUEE })
          .maxLines(1)
          .fontSize(12)
          .border({ width: 1 })
          .padding(10)

  Text('API 18 属性')
    Text('当文本溢出其尺寸时,文本将滚动显示,支持设置跑马灯配置项 When the text overflows its dimensions, the text will scroll for displaying.')
          .width(250)
          .textOverflow({ overflow: TextOverflow.MARQUEE })
          .maxLines(1)
          .fontSize(12)
          .border({ width: 1 })
          .padding(10)
          .marqueeOptions({
            start: true,
            fromStart: true,
            step: 6,
            loop: -1,
            delay: 0,
            fadeout: false,
            marqueeStartPolicy: MarqueeStartPolicy.DEFAULT
          })

五、字体自适应与大小写

  • 自适应字号:
    • 必须同时设置 minFontSizemaxFontSize
    • 配合 maxLines 或容器尺寸
  • 大小写控制:textCase(Normal/LowerCase/UpperCase)
//minFontSize用于设置文本的最小显示字号,maxFontSize用于设置文本的最大显示字号。这两个属性必须同时设置才能生效,并且需要与maxLines属性或布局大小限制配合使用,单独设置任一属性将不会产生效果
        Text('我的最大字号为30,最小字号为5,宽度为250,maxLines为1')
          .width(250)
          .maxLines(1)
          .maxFontSize(30)
          .minFontSize(5)
          .border({ width: 1 })
          .padding(10)
          .margin(5)
        Text('我的最大字号为30,最小字号为5,宽度为250,maxLines为2')
          .width(250)
          .maxLines(2)
          .maxFontSize(30)
          .minFontSize(5)
          .border({ width: 1 })
          .padding(10)
          .margin(5)
        Text('我的最大字号为30,最小字号为15,宽度为250,高度为50')
          .width(250)
          .height(50)
          .maxFontSize(30)
          .minFontSize(15)
          .border({ width: 1 })
          .padding(10)
          .margin(5)
        Text('我的最大字号为30,最小字号为15,宽度为250,高度为100')
          .width(250)
          .height(100)
          .maxFontSize(30)
          .minFontSize(15)
          .border({ width: 1 })
          .padding(10)
          .margin(5)

        Text('textCase属性设置文本大小写').fontSize(20).fontColor(Color.Green).fontWeight(700)
        Text('This is the text content with textCase set to Normal.')
          .textCase(TextCase.Normal)
          .padding(10)
          .border({ width: 1 })
          .padding(10)
          .margin(5)

        // 文本全小写展示
        Text('This is the text content with textCase set to LowerCase.')
          .textCase(TextCase.LowerCase)
          .border({ width: 1 })
          .padding(10)
          .margin(5)

        // 文本全大写展示
        Text('This is the text content with textCase set to UpperCase.')
          .textCase(TextCase.UpperCase)
          .border({ width: 1 })
          .padding(10)
          .margin(5)

六、复制与交互

  • 文本可复制:copyOption(CopyOptions.InApp)
Text('copyOption属性设置文本是否可复制粘贴').fontSize(20).fontColor(Color.Green).fontWeight(700)
        Text('这是一段可复制文本。')
          .fontSize(30)
          .copyOption(CopyOptions.InApp)

        Text('fontFamily属性设置字体列表。应用当前支持\'HarmonyOS Sans\'字体和注册自定义字体。')
          .fontSize(20)
          .fontColor(Color.Green)
          .fontWeight(700)
        Text('This is the text content with fontFamily')
          .fontSize(30)
          .fontFamily('HarmonyOS Sans')

七、API 20 特性汇总

  • 数字翻牌:contentTransition(NumericTextTransition)
  • 行尾空格优化:optimizeTrailingSpace(true/false)
  • 中西文自动间距:enableAutoSpacing(...)
  • 渐变文字:shaderStyle(linearGradientOptions)
  • 行间距:lineSpacing
//API version 20开始,支持通过contentTransition属性设置数字翻牌效果
        Text('API 20 contentTransition属性设置数字翻牌效果').fontSize(20).fontColor(Color.Green).fontWeight(700)
        Text(this.number + '')
          .borderWidth(1)
          .fontSize(40)
          .contentTransition(this.numberTransition)
        Button('chang number')
          .onClick(() => {
            this.number++
          })
          .margin(10)

        // 支持通过optimizeTrailingSpace设置是否在文本布局过程中优化每行末尾的空格,可解决行尾空格影响对齐显示效果问题
        Text('API 20 通过optimizeTrailingSpace设置是否在文本布局过程中优化每行末尾的空格,可解决行尾空格影响对齐显示效果问题')
          .fontSize(20)
          .fontColor(Color.Green)
          .fontWeight(700)
        // 启用优化行尾空格功能
        Text('Trimmed space enabled     ')
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
          .margin({ top: 20 })
          .optimizeTrailingSpace(true)
          .textAlign(TextAlign.Center)
        // 不启用优化行尾空格功能
        Text('Trimmed space disabled     ')
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
          .margin({ top: 20 })
          .optimizeTrailingSpace(false)
          .textAlign(TextAlign.Center)
Text('API 20,支持通过lineSpacing设置文本的行间距').fontSize(20).fontColor(Color.Green).fontWeight(700)
        Text('The line spacing of this context is set to 20_px, and the spacing is effective only between the lines.')
          .lineSpacing(LengthMetrics.px(20), { onlyBetweenLines: true })
          .width(250)
          .height(100)
          .maxFontSize(30)
          .minFontSize(15)
          .border({ width: 1 })

        Text('API 20,enableAutoSpacing设置是否开启中文与西文的自动间距')
          .fontSize(20)
          .fontColor(Color.Green)
          .fontWeight(700)
        Text(this.enableSpacing ? '已开启自动间距' : '当前状态:已关闭自动间距')
          .fontSize(16)
          .fontColor(this.enableSpacing ? '#4CAF50' : '#F44336')
          .margin({ bottom: 20 })

        // 设置是否应用中西文自动间距
        Text('中西文Auto Spacing自动间距')
          .fontSize(24)
          .padding(15)
          .backgroundColor('#F5F5F5')
          .width('90%')
          .enableAutoSpacing(this.enableSpacing)
          .onClick(() => {
            this.enableSpacing = !this.enableSpacing;
          })

八、渐变文字实现方式

  • API 20 前:Column + linearGradient + blendMode 实现裁切渐变
  • API 20 起:直接 shaderStyle
   Text('API 20 前, shaderStyle设置渐变色').fontSize(20).fontColor(Color.Green).fontWeight(700)
        Column() {
          Text(`API 20 前, shaderStyle设置渐变色`)
            .fontSize(24)
            .fontWeight(900)
            .blendMode(BlendMode.DST_IN, BlendApplyType.OFFSCREEN) // 关键:裁切背景
        }
        .linearGradient({
          angle: 0,
          colors: [['#F8E74F', 0], ['#67E447', 1]]
        })
        .blendMode(BlendMode.SRC_OVER, BlendApplyType.OFFSCREEN)


        //PI version 20开始,支持通过shaderStyle设置渐变色
        Text('API 20, shaderStyle设置渐变色').fontSize(20).fontColor(Color.Green).fontWeight(700)
        Text('API 20, shaderStyle设置渐变色')
          .fontSize(24)
          .fontWeight(900)
          .width('80%')
          .shaderStyle(this.linearGradientOptions)

九、图文混排

  • ImageSpanSpan 混排
  • ImageSpanAlignment.FOLLOW_PARAGRAPH 对齐基线
  • textVerticalAlign 控制段落垂直对齐

        Text('API 20,textVerticalAlign属性实现文本段落在垂直方向的对齐').fontSize(20).fontColor(Color.Green).fontWeight(700)

        Text() {
          Span('听原音')
            .fontSize(50)
          ImageSpan($r('app.media.ic_dubbing_listen'))
            .width(30).height(30)
            .verticalAlign(ImageSpanAlignment.FOLLOW_PARAGRAPH)
          Span('去配音')
        }
        .padding(10)
        .textVerticalAlign(TextVerticalAlign.CENTER)
        .borderWidth(1).borderRadius(12).borderColor(Color.Blue)


        Text('这是一段文本,用来展示选中菜单')
          .fontSize(30)
          .copyOption(CopyOptions.InApp)

        Text() {
          Span('今天是')                // 普通文字

          Span('2024年12月24日')        // 第一张便利贴(红色)
            .fontColor(Color.Red)
            .fontWeight(FontWeight.Bold)

          Span(',天气')                // 又回到普通文字

          Span('晴朗')                  // 第二张便利贴(蓝色+下划线)
            .fontColor(Color.Blue)
            .decoration({ type: TextDecorationType.Underline })

          Span('。')                   // 结束
        }

十、实战示例

1. 自适应字号 + 省略

  • 输入框驱动文本变化
  • 使用 maxFontSize/minFontSize + ellipsisMode + maxLines
 Column({ space: 30 }) {
      TextInput({ text: this.example1 })
        .onChange((value: string) => {
          // 文本内容发生变化时触发该回调
          this.example1 = value;
        })
      Row() {
        Text('大')
          .width(40)
          .height(40)
          .backgroundColor(Color.Orange)
          .fontSize(35)
        Text(this.example1)
          .maxFontSize(30)
          .minFontSize(15)
          .constraintSize({
            minWidth: 20,
            maxWidth: '100%'
          })
          .ellipsisMode(EllipsisMode.END)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .maxLines(1)
          .textAlign(TextAlign.Center)
          .fontColor(Color.Black)
          .fontWeight(500)
          .layoutWeight(1)
          .height(40)
        Text('小')
          .width(40)
          .height(40)
          .backgroundColor(Color.Orange)
          .fontSize(15).textAlign(TextAlign.Center)
      }
      .width('100%')
      .height('auto')

    }
    .height('auto')
    .width('100%')

2. 混排文本截断异常

  • 采用 WordBreak.BREAK_ALL 逐字截断
 Text('实战2:如何解决Text组件文本为中文、数字、英文混合时显示省略号截断异常的问题').fontSize(20).fontColor(Color.Green).fontWeight(700)
        Text('2 years · VIP membership for 3 months · 8GB · 230mm · Product color\u200B')
          .width(200)// Set maximum number of rows
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })// Long text display ellipsis
          .ellipsisMode(EllipsisMode.END)// Set the line breaking rule WordBreak.BREAK_ALL and implement truncation on a letter by letter basis
          .wordBreak(WordBreak.BREAK_ALL)// API11+ required, for letter-level truncation
          .textAlign(TextAlign.JUSTIFY)
          .backgroundColor(Color.Green)
          .fontSize(16)

3. 热搜列表布局

  • Column + Row 组合
  • 序号、标签、热度标识统一布局与约束宽度
        Column() {
            Row() {
              Text('1').fontSize(14).fontColor(Color.Red).margin({ left: 10, right: 10 })
              Text('我是热搜词条1')
                .fontSize(12)
                .fontColor(Color.Blue)
                .maxLines(1)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
                .fontWeight(300)
              Text('爆')
                .margin({ left: 6 })
                .textAlign(TextAlign.Center)
                .fontSize(10)
                .fontColor(Color.White)
                .fontWeight(600)
                .backgroundColor(0x770100)
                .borderRadius(5)
                .width(15)
                .height(14)
            }.width('100%').margin(5)

            Row() {
              Text('2').fontSize(14).fontColor(Color.Red).margin({ left: 10, right: 10 })
              Text('我是热搜词条2 我是热搜词条2 我是热搜词条2 我是热搜词条2 我是热搜词条2')
                .fontSize(12)
                .fontColor(Color.Blue)
                .fontWeight(300)
                .constraintSize({ maxWidth: 200 })
                .maxLines(1)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
              Text('热')
                .margin({ left: 6 })
                .textAlign(TextAlign.Center)
                .fontSize(10)
                .fontColor(Color.White)
                .fontWeight(600)
                .backgroundColor(0xCC5500)
                .borderRadius(5)
                .width(15)
                .height(14)
            }.width('100%').margin(5)

            Row() {
              Text('3').fontSize(14).fontColor(Color.Orange).margin({ left: 10, right: 10 })
              Text('我是热搜词条3')
                .fontSize(12)
                .fontColor(Color.Blue)
                .fontWeight(300)
                .maxLines(1)
                .constraintSize({ maxWidth: 200 })
                .textOverflow({ overflow: TextOverflow.Ellipsis })
              Text('热')
                .margin({ left: 6 })
                .textAlign(TextAlign.Center)
                .fontSize(10)
                .fontColor(Color.White)
                .fontWeight(600)
                .backgroundColor(0xCC5500)
                .borderRadius(5)
                .width(15)
                .height(14)
            }.width('100%').margin(5)

            Row() {
              Text('4').fontSize(14).fontColor(Color.Grey).margin({ left: 10, right: 10 })
              Text('我是热搜词条4 我是热搜词条4 我是热搜词条4 我是热搜词条4 我是热搜词条4')
                .fontSize(12)
                .fontColor(Color.Blue)
                .fontWeight(300)
                .constraintSize({ maxWidth: 200 })
                .maxLines(1)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
            }.width('100%').margin(5)
          }.width('100%').height('auto') .padding({ left: 12, right: 12 })

一句话总结

Text 和 Span 就像“记事纸和便利贴”:

  • Text = 完整的纸张,展示大段文字 + 支持选中自定义菜单
  • Span = 彩色便利贴,装饰局部文字(改颜色、加粗、变样式)
  • 关系:便利贴(Span)必须贴在纸(Text)上才能用
  • 特色功能:Text可以让你自定义选中文字后的菜单(复制/翻译/分享等)

最后上图,如需要源码,请私信。

在这里插入图片描述

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐