• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 属性字符串(StyledString/MutableStyledString2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @hddgzw-->
5<!--Designer: @pssea-->
6<!--Tester: @jiaoaozihao-->
7<!--Adviser: @HelloCrease-->
8
9属性字符串StyledString/MutableStyledString(其中MutableStyledString继承自StyledString,下文统称为StyledString),可用于在字符或段落级别上设置文本样式。将StyledString应用到文本组件上,可以采用多种方式修改文本,包括调整字号、添加字体颜色、使文本具备可点击性,以及通过自定义方式绘制文本等。具体使用方法请参考[属性字符串](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md)的文档。
10
11属性字符串提供多种类型样式对象,涵盖各种常见的文本样式格式,例如文本装饰线样式、文本行高样式、文本阴影样式等。也可以自行创建CustomSpan,以应用自定义样式。
12
13## 创建并应用StyledString和MutableStyledString
14
15  可以通过TextController提供的[setStyledString](../reference/apis-arkui/arkui-ts/ts-basic-components-text.md#setstyledstring12)方法,将属性字符串附加到文本组件,并推荐在[onPageShow](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#onpageshow)或者文本组件的[onAppear](../reference/apis-arkui/arkui-ts/ts-universal-events-show-hide.md#onappear)回调中触发绑定。
16  > **说明:**
17  >
18  > 在aboutToAppear中调用setStyledString方法时,由于该方法运行阶段组件尚未完成创建并成功挂载节点树,因此无法在页面初始化时显示属性字符串。
19  >
20  > 从API version 15开始,在aboutToAppear中调用setStyledString方法,页面初始化时可以显示属性字符串。
21
22  ```ts
23  @Entry
24  @Component
25  struct styled_string_demo1 {
26    styledString1: StyledString = new StyledString("运动45分钟");
27    mutableStyledString1: MutableStyledString = new MutableStyledString("运动35分钟");
28    controller1: TextController = new TextController();
29    controller2: TextController = new TextController();
30
31    async onPageShow() {
32      // 在生命周期onPageShow回调中绑定属性字符串
33      this.controller1.setStyledString(this.styledString1);
34    }
35
36    build() {
37      Column() {
38        // 显示属性字符串
39        Text(undefined, { controller: this.controller1 })
40        Text(undefined, { controller: this.controller2 })
41          .onAppear(() => {
42            // 在组件onAppear回调中绑定属性字符串
43            this.controller2.setStyledString(this.mutableStyledString1);
44          })
45      }
46      .width('100%')
47    }
48  }
49  ```
50  ![StyledString_Init](figures/span_string_init.png)
51
52## 设置文本样式
53
54属性字符串目前提供了多种Style对象,包括[TextStyle](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#textstyle)、[TextShadowStyle](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#textshadowstyle)、[DecorationStyle](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#decorationstyle)、[BaselineOffsetStyle](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#baselineoffsetstyle)、[LineHeightStyle](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#lineheightstyle)、[LetterSpacingStyle](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#letterspacingstyle),用于设置文本的各类样式。
55
56- 创建及应用文本字体样式对象(TextStyle)
57
58  ```ts
59  import { LengthMetrics } from '@kit.ArkUI';
60
61  @Entry
62  @Component
63  struct styled_string_demo2 {
64    textStyleAttrs: TextStyle =
65      new TextStyle({ fontWeight: FontWeight.Bolder, fontSize: LengthMetrics.vp(24), fontStyle: FontStyle.Italic, strokeWidth: LengthMetrics.px(5), strokeColor: Color.Green });
66    mutableStyledString: MutableStyledString = new MutableStyledString("运动45分钟 目标达成", [
67      {
68        start: 2,
69        length: 2,
70        styledKey: StyledStringKey.FONT,
71        styledValue: this.textStyleAttrs
72      },
73      {
74        start: 7,
75        length: 4,
76        styledKey: StyledStringKey.FONT,
77        styledValue: new TextStyle({ fontColor: Color.Orange, fontSize: LengthMetrics.vp(12),
78        superscript: SuperscriptStyle.SUPERSCRIPT })
79      }
80    ]);
81    controller: TextController = new TextController();
82
83    async onPageShow() {
84      this.controller.setStyledString(this.mutableStyledString);
85    }
86
87    build() {
88      Column() {
89        // 显示属性字符串
90        Text(undefined, { controller: this.controller })
91          .margin({ top: 10 })
92      }
93      .width('100%')
94    }
95  }
96  ```
97  ![StyledString_TextStyle](figures/StyledString_TextStyle.png)
98
99- 创建及应用文本阴影对象(TextShadowStyle)
100
101  ```ts
102  // xxx.ets
103  @Entry
104  @Component
105  struct styled_string_demo3 {
106    mutableStyledString: MutableStyledString = new MutableStyledString("运动35分钟", [
107      {
108        start: 0,
109        length: 3,
110        styledKey: StyledStringKey.TEXT_SHADOW,
111        styledValue: new TextShadowStyle({
112          radius: 5,
113          type: ShadowType.COLOR,
114          color: Color.Red,
115          offsetX: 10,
116          offsetY: 10
117        })
118      }
119    ]);
120    controller: TextController = new TextController();
121
122    async onPageShow() {
123      this.controller.setStyledString(this.mutableStyledString);
124    }
125
126    build() {
127      Column() {
128        // 显示属性字符串
129        Text(undefined, { controller: this.controller })
130      }
131      .width('100%')
132    }
133  }
134  ```
135  ![StyledString_TextShadow](figures/styled_string_text_shadow.png)
136
137- 创建及应用文本装饰线对象(DecorationStyle)
138
139  ```ts
140  // xxx.ets
141  @Entry
142  @Component
143  struct styled_string_demo4 {
144    mutableStyledString: MutableStyledString = new MutableStyledString("运动35分钟", [
145      {
146        start: 0,
147        length: 4,
148        styledKey: StyledStringKey.DECORATION,
149        styledValue: new DecorationStyle({ type: TextDecorationType.LineThrough, color: Color.Red, thicknessScale: 3 })
150      },
151      {
152        start: 4,
153        length: 2,
154        styledKey: StyledStringKey.DECORATION,
155        styledValue: new DecorationStyle(
156          {
157            type: TextDecorationType.Underline,
158          },
159          {
160            // 开启多装饰线
161            enableMultiType: true
162          }
163        )
164      },
165      {
166        start: 4,
167        length: 2,
168        styledKey: StyledStringKey.DECORATION,
169        styledValue: new DecorationStyle(
170          {
171            type: TextDecorationType.LineThrough,
172          },
173          {
174            // 开启多装饰线
175            enableMultiType: true
176          }
177        )
178      },
179    ]);
180    controller: TextController = new TextController();
181
182    async onPageShow() {
183      this.controller.setStyledString(this.mutableStyledString);
184    }
185
186    build() {
187      Column() {
188        // 显示属性字符串
189        Text(undefined, { controller: this.controller })
190      }
191      .width('100%')
192    }
193  }
194  ```
195  ![StyledString_Decoration](figures/styled_string_decoration.jpg)
196
197- 创建及应用文本基线偏移量对象(BaselineOffsetStyle)
198
199  ```ts
200  import { LengthMetrics } from '@kit.ArkUI';
201
202  // xxx.ets
203  @Entry
204  @Component
205  struct styled_string_demo5 {
206    mutableStyledString: MutableStyledString = new MutableStyledString("运动35分钟", [
207      {
208        start: 0,
209        length: 3,
210        styledKey: StyledStringKey.BASELINE_OFFSET,
211        styledValue: new BaselineOffsetStyle(LengthMetrics.px(20))
212      }
213    ]);
214    controller: TextController = new TextController();
215
216    async onPageShow() {
217      this.controller.setStyledString(this.mutableStyledString);
218    }
219
220    build() {
221      Column() {
222        // 显示属性字符串
223        Text(undefined, { controller: this.controller })
224      }
225      .width('100%')
226    }
227  }
228  ```
229  ![StyledString_Baseline](figures/styled_string_baselineoffset.png)
230
231- 创建及应用文本行高对象(LineHeightStyle)
232
233  ```ts
234  import { LengthMetrics } from '@kit.ArkUI';
235
236  // xxx.ets
237  @Entry
238  @Component
239  struct styled_string_demo6 {
240    mutableStyledString: MutableStyledString = new MutableStyledString("运动35分钟\n顶顶顶\n得到", [
241      {
242        start: 8,
243        length: 3,
244        styledKey: StyledStringKey.LINE_HEIGHT,
245        styledValue: new LineHeightStyle(LengthMetrics.vp(50))
246      }
247    ]);
248    controller: TextController = new TextController();
249
250    async onPageShow() {
251      this.controller.setStyledString(this.mutableStyledString);
252    }
253
254    build() {
255      Column() {
256        // 显示属性字符串
257        Text(undefined, { controller: this.controller })
258      }
259      .width('100%')
260      .margin({ top: 10 })
261    }
262  }
263  ```
264  ![StyledString_lineHeight](figures/styled_string_lineHeight.png)
265
266- 创建及应用文本字符间距对象(LetterSpacingStyle)
267
268  ```ts
269  import { LengthMetrics, LengthUnit } from '@kit.ArkUI';
270
271  // xxx.ets
272  @Entry
273  @Component
274  struct styled_string_demo7 {
275    mutableStyledString: MutableStyledString = new MutableStyledString("运动35分钟", [
276      {
277        start: 0,
278        length: 2,
279        styledKey: StyledStringKey.LETTER_SPACING,
280        styledValue: new LetterSpacingStyle(new LengthMetrics(20, LengthUnit.VP))
281      }
282    ]);
283    controller: TextController = new TextController();
284
285    async onPageShow() {
286      this.controller.setStyledString(this.mutableStyledString);
287    }
288
289    build() {
290      Column() {
291        // 显示属性字符串
292        Text(undefined, { controller: this.controller })
293      }
294      .width('100%')
295    }
296  }
297  ```
298  ![StyledString_letterSpacing](figures/styled_string_letterspacing.png)
299
300## 设置段落样式
301
302可通过[ParagraphStyle](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#paragraphstyle)设置段落样式布局。下图显示了如何分割文本中的段落,段落以换行符 \n 结尾。
303
304![paragraphs](figures/styledstringParagraphs.png)
305
306以下代码示例展示了如何创建ParagraphStyle并应用。如果将ParagraphStyle附加到段落开头、末尾或之间的任何位置,均会应用样式,非段落区间内则不会应用样式。
307
308  ```ts
309  import { LengthMetrics} from '@kit.ArkUI';
310
311  // xxx.ets
312  @Entry
313  @Component
314  struct Index {
315    titleParagraphStyleAttr: ParagraphStyle = new ParagraphStyle({ textAlign: TextAlign.Center });
316    // 段落首行缩进15vp
317    paragraphStyleAttr1: ParagraphStyle = new ParagraphStyle({ textIndent: LengthMetrics.vp(15) });
318    // 行高样式对象
319    lineHeightStyle1: LineHeightStyle = new LineHeightStyle(new LengthMetrics(24));
320    // 创建含段落样式的对象paragraphStyledString1
321    paragraphStyledString1: MutableStyledString =
322      new MutableStyledString("段落标题\n正文第一段落开始0123456789正文第一段落结束。", [
323        {
324          start: 0,
325          length: 4,
326          styledKey: StyledStringKey.PARAGRAPH_STYLE,
327          styledValue: this.titleParagraphStyleAttr
328        },
329        {
330          start: 0,
331          length: 4,
332          styledKey: StyledStringKey.LINE_HEIGHT,
333          styledValue: new LineHeightStyle(new LengthMetrics(50))
334        }, {
335        start: 0,
336        length: 4,
337        styledKey: StyledStringKey.FONT,
338        styledValue: new TextStyle({ fontSize: LengthMetrics.vp(24), fontWeight: FontWeight.Bolder })
339      },
340        {
341          start: 5,
342          length: 3,
343          styledKey: StyledStringKey.PARAGRAPH_STYLE,
344          styledValue: this.paragraphStyleAttr1
345        },
346        {
347          start: 5,
348          length: 20,
349          styledKey: StyledStringKey.LINE_HEIGHT,
350          styledValue: this.lineHeightStyle1
351        }
352      ]);
353    controller: TextController = new TextController();
354
355    async onPageShow() {
356      this.controller.setStyledString(this.paragraphStyledString1);
357    }
358
359    build() {
360      Column() {
361        // 显示属性字符串
362        Text(undefined, { controller: this.controller })
363      }
364      .width('100%')
365    }
366  }
367  ```
368
369  ![styled_string_paragraph1](figures/styled_string_paragraph1.png)
370
371  除了可以在创建属性字符串时就预设样式,也可以后续通过[replaceStyle](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#replacestyle)清空原样式替换新样式,同时需要在附加的文本组件controller上主动触发更新绑定的属性字符串。
372
373  ```ts
374  import { LengthMetrics } from '@kit.ArkUI';
375
376  // xxx.ets
377  @Entry
378  @Component
379  struct Index {
380    titleParagraphStyleAttr: ParagraphStyle = new ParagraphStyle({ textAlign: TextAlign.Center });
381    // 段落首行缩进15vp
382    paragraphStyleAttr1: ParagraphStyle = new ParagraphStyle({ textIndent: LengthMetrics.vp(15) });
383    // 行高样式对象
384    lineHeightStyle1: LineHeightStyle = new LineHeightStyle(new LengthMetrics(24));
385    // 创建含段落样式的对象paragraphStyledString1
386    paragraphStyledString1: MutableStyledString =
387      new MutableStyledString("段落标题\n正文第一段落开始0123456789正文第一段落结束,通过replaceStyle清空原样式替换新样式。", [
388        {
389          start: 0,
390          length: 4,
391          styledKey: StyledStringKey.PARAGRAPH_STYLE,
392          styledValue: this.titleParagraphStyleAttr
393        },
394        {
395          start: 0,
396          length: 4,
397          styledKey: StyledStringKey.LINE_HEIGHT,
398          styledValue: new LineHeightStyle(new LengthMetrics(50))
399        }, {
400        start: 0,
401        length: 4,
402        styledKey: StyledStringKey.FONT,
403        styledValue: new TextStyle({ fontSize: LengthMetrics.vp(24), fontWeight: FontWeight.Bolder })
404      },
405        {
406          start: 5,
407          length: 3,
408          styledKey: StyledStringKey.PARAGRAPH_STYLE,
409          styledValue: this.paragraphStyleAttr1
410        },
411        {
412          start: 5,
413          length: 20,
414          styledKey: StyledStringKey.LINE_HEIGHT,
415          styledValue: this.lineHeightStyle1
416        }
417      ]);
418    paragraphStyleAttr3: ParagraphStyle = new ParagraphStyle({
419      textAlign: TextAlign.End,
420      maxLines: 1,
421      wordBreak: WordBreak.BREAK_ALL,
422      overflow: TextOverflow.Ellipsis
423    });
424    controller: TextController = new TextController();
425
426    async onPageShow() {
427      this.controller.setStyledString(this.paragraphStyledString1);
428    }
429
430    build() {
431      Column() {
432        // 显示属性字符串
433        Text(undefined, { controller: this.controller }).width(300)
434        Button('替换段落样式')
435          .onClick(() => {
436            this.paragraphStyledString1.replaceStyle({
437              start: 5,
438              length: 3,
439              styledKey: StyledStringKey.PARAGRAPH_STYLE,
440              styledValue: this.paragraphStyleAttr3
441            });
442            this.controller.setStyledString(this.paragraphStyledString1);
443          })
444      }
445      .width('100%')
446    }
447  }
448  ```
449
450  ![styled_string_paragraph2](figures/styled_string_paragraph2.gif)
451
452## 支持将属性字符串转换成Paragraph
453
454可通过[getParagraphs](../reference/apis-arkui/arkts-apis-uicontext-measureutils.md#getparagraphs20)将属性字符串根据文本布局选项转换成对应的[Paragraph](../reference/apis-arkgraphics2d/js-apis-graphics-text.md#paragraph)数组。
455
456- 以下示例展示了通过MeasureUtils的getParagraphs方法测算文本,当内容超出最大显示行数的时候,截断文本显示并展示“...全文”的效果。
457
458  ```ts
459  import { LengthMetrics } from '@kit.ArkUI';
460  import { drawing } from '@kit.ArkGraphics2D';
461
462  class MyCustomSpan extends CustomSpan {
463    constructor(word: string, width: number, height: number, context: UIContext) {
464      super();
465      this.word = word;
466      this.width = width;
467      this.height = height;
468      this.context = context;
469    }
470
471    onMeasure(measureInfo: CustomSpanMeasureInfo): CustomSpanMetrics {
472      return { width: this.width, height: this.height };
473    }
474
475    onDraw(context: DrawContext, options: CustomSpanDrawInfo) {
476      let canvas = context.canvas;
477      const brush = new drawing.Brush();
478      brush.setColor({
479        alpha: 255,
480        red: 0,
481        green: 74,
482        blue: 175
483      });
484      const font = new drawing.Font();
485      font.setSize(25);
486      const textBlob = drawing.TextBlob.makeFromString(this.word, font, drawing.TextEncoding.TEXT_ENCODING_UTF8);
487      canvas.attachBrush(brush);
488      canvas.drawRect({
489        left: options.x + 10,
490        right: options.x + this.context.vp2px(this.width) - 10,
491        top: options.lineTop + 10,
492        bottom: options.lineBottom - 10
493      });
494      brush.setColor({
495        alpha: 255,
496        red: 23,
497        green: 169,
498        blue: 141
499      });
500      canvas.attachBrush(brush);
501      canvas.drawTextBlob(textBlob, options.x + 20, options.lineBottom - 15);
502      canvas.detachBrush();
503    }
504
505    setWord(word: string) {
506      this.word = word;
507    }
508
509    width: number = 160;
510    word: string = "drawing";
511    height: number = 10;
512    context: UIContext;
513  }
514
515  @Entry
516  @Component
517  struct Index {
518    str: string =
519      "Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.";
520    mutableStr2 = new MutableStyledString(this.str, [
521      {
522        start: 0,
523        length: 3,
524        styledKey: StyledStringKey.FONT,
525        styledValue: new TextStyle({ fontSize: LengthMetrics.px(20) })
526      },
527      {
528        start: 3,
529        length: 3,
530        styledKey: StyledStringKey.FONT,
531        styledValue: new TextStyle({ fontColor: Color.Brown })
532      }
533    ]);
534
535    // 测算属性字符串在指定宽度下能显示的行数
536    getLineNum(styledString: StyledString, width: LengthMetrics) {
537      let paragraphArr = this.getUIContext().getMeasureUtils().getParagraphs(styledString, { constraintWidth: width });
538      let res = 0;
539      for (let i = 0; i < paragraphArr.length; ++i) {
540        res += paragraphArr[i].getLineCount();
541      }
542      return res;
543    }
544
545    // 测算属性字符串显示maxLines行时最多可以显示的字数
546    getCorrectIndex(styledString: MutableStyledString, maxLines: number, width: LengthMetrics) {
547      let low = 0;
548      let high = styledString.length - 1;
549      // 使用二分查找
550      while (low <= high) {
551        let mid = (low + high) >> 1;
552        console.log("demo: get " + low + " " + high + " " + mid);
553        let moreStyledString = new MutableStyledString("... 全文", [{
554          start: 4,
555          length: 2,
556          styledKey: StyledStringKey.FONT,
557          styledValue: new TextStyle({ fontColor: Color.Blue })
558        }]);
559        moreStyledString.insertStyledString(0, styledString.subStyledString(0, mid));
560        let lineNum = this.getLineNum(moreStyledString, LengthMetrics.px(500));
561        if (lineNum <= maxLines) {
562          low = mid + 1;
563        } else {
564          high = mid - 1;
565        }
566      }
567      return high;
568    }
569
570    mutableStrAllContent = new MutableStyledString(this.str, [
571      {
572        start: 0,
573        length: 3,
574        styledKey: StyledStringKey.FONT,
575        styledValue: new TextStyle({ fontSize: LengthMetrics.px(40) })
576      },
577      {
578        start: 3,
579        length: 3,
580        styledKey: StyledStringKey.FONT,
581        styledValue: new TextStyle({ fontColor: Color.Brown })
582      }
583    ]);
584    customSpan1: MyCustomSpan = new MyCustomSpan("Hello", 120, 10, this.getUIContext());
585    mutableStrAllContent2 = new MutableStyledString(this.str, [
586      {
587        start: 0,
588        length: 3,
589        styledKey: StyledStringKey.FONT,
590        styledValue: new TextStyle({ fontSize: LengthMetrics.px(100) })
591      },
592      {
593        start: 3,
594        length: 3,
595        styledKey: StyledStringKey.FONT,
596        styledValue: new TextStyle({ fontColor: Color.Brown })
597      }
598    ]);
599    controller: TextController = new TextController();
600    controller2: TextController = new TextController();
601    textController: TextController = new TextController();
602    textController2: TextController = new TextController();
603
604    aboutToAppear() {
605      this.mutableStrAllContent2.insertStyledString(0, new StyledString(this.customSpan1));
606      this.mutableStr2.insertStyledString(0, new StyledString(this.customSpan1));
607    }
608
609    build() {
610      Scroll() {
611        Column() {
612          Text('原文')
613          Text(undefined, { controller: this.controller }).width('500px').onAppear(() => {
614            this.controller.setStyledString(this.mutableStrAllContent);
615          })
616          Divider().strokeWidth(8).color('#F1F3F5')
617          Text('排版后')
618          Text(undefined, { controller: this.textController }).onAppear(() => {
619            let now = this.getCorrectIndex(this.mutableStrAllContent, 3, LengthMetrics.px(500));
620            if (now != this.mutableStrAllContent.length - 1) {
621              let moreStyledString = new MutableStyledString("... 全文", [{
622                start: 4,
623                length: 2,
624                styledKey: StyledStringKey.FONT,
625                styledValue: new TextStyle({ fontColor: Color.Blue })
626              }]);
627              moreStyledString.insertStyledString(0, this.mutableStrAllContent.subStyledString(0, now));
628              this.textController.setStyledString(moreStyledString);
629            } else {
630              this.textController.setStyledString(this.mutableStrAllContent);
631            }
632          })
633            .width('500px')
634          Divider().strokeWidth(8).color('#F1F3F5')
635          Text('原文')
636          Text(undefined, { controller: this.controller2 }).width('500px').onAppear(() => {
637            this.controller2.setStyledString(this.mutableStrAllContent2);
638          })
639          Divider().strokeWidth(8).color('#F1F3F5')
640          Text('排版后')
641          Text(undefined, { controller: this.textController2 }).onAppear(() => {
642            let now = this.getCorrectIndex(this.mutableStrAllContent2, 3, LengthMetrics.px(500));
643            let moreStyledString = new MutableStyledString("... 全文", [{
644              start: 4,
645              length: 2,
646              styledKey: StyledStringKey.FONT,
647              styledValue: new TextStyle({ fontColor: Color.Blue })
648            }]);
649            moreStyledString.insertStyledString(0, this.mutableStrAllContent2.subStyledString(0, now));
650            this.textController2.setStyledString(moreStyledString);
651          })
652            .width('500px')
653        }.width('100%')
654      }
655    }
656  }
657  ```
658
659  ![StyledString_GetParagraphs](figures/StyledString_GetParagraphs.png)
660
661
662## 使用图片
663
664可通过[ImageAttachment](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#imageattachment)来添加图片。
665
666以下示例展示了如何将图片和文本附加到同一个MutableStyledString对象上,并实现图文混排。
667
668  ```ts
669  // xxx.ets
670  import { image } from '@kit.ImageKit';
671  import { LengthMetrics } from '@kit.ArkUI';
672
673  @Entry
674  @Component
675  struct styled_string_demo4 {
676    @State message: string = 'Hello World';
677    imagePixelMap: image.PixelMap | undefined = undefined;
678    @State imagePixelMap3: image.PixelMap | undefined = undefined;
679    mutableStr: MutableStyledString = new MutableStyledString('123');
680    controller: TextController = new TextController();
681    mutableStr2: MutableStyledString = new MutableStyledString('This is set decoration line style to the mutableStr2', [{
682      start: 0,
683      length: 15,
684      styledKey: StyledStringKey.DECORATION,
685      styledValue: new DecorationStyle({
686        type: TextDecorationType.Overline,
687        color: Color.Orange,
688        style: TextDecorationStyle.DOUBLE
689      })
690    }]);
691
692    async aboutToAppear() {
693      console.info("aboutToAppear initial imagePixelMap");
694      // $r('app.media.sea')需要替换为开发者所需的图像资源文件。
695      this.imagePixelMap = await this.getPixmapFromMedia($r('app.media.sea'));
696    }
697
698    private async getPixmapFromMedia(resource: Resource) {
699      let unit8Array = await this.getUIContext().getHostContext()?.resourceManager?.getMediaContent(resource.id);
700      let imageSource = image.createImageSource(unit8Array?.buffer?.slice(0, unit8Array?.buffer?.byteLength));
701      let createPixelMap: image.PixelMap = await imageSource.createPixelMap({
702        desiredPixelFormat: image.PixelMapFormat.RGBA_8888
703      });
704      await imageSource.release();
705      return createPixelMap;
706    }
707
708    leadingMarginValue: ParagraphStyle = new ParagraphStyle({ leadingMargin: LengthMetrics.vp(5)});
709    // 行高样式对象
710    lineHeightStyle1: LineHeightStyle= new LineHeightStyle(new LengthMetrics(24));
711    // Bold样式
712    boldTextStyle: TextStyle = new TextStyle({ fontWeight: FontWeight.Bold });
713    // 创建含段落样式的对象paragraphStyledString1
714    paragraphStyledString1: MutableStyledString = new MutableStyledString("\n品牌相纸 高清冲印30张\n限时直降5.15元 限量增送", [
715      {
716        start: 0,
717        length: 28,
718        styledKey: StyledStringKey.PARAGRAPH_STYLE,
719        styledValue: this.leadingMarginValue
720      },
721      {
722        start: 14,
723        length: 9,
724        styledKey: StyledStringKey.FONT,
725        styledValue: new TextStyle({ fontSize: LengthMetrics.vp(14), fontColor: '#B22222' })
726      },
727      {
728        start: 24,
729        length: 4,
730        styledKey: StyledStringKey.FONT,
731        styledValue: new TextStyle({ fontSize: LengthMetrics.vp(14), fontWeight: FontWeight.Lighter })
732      },
733      {
734        start: 11,
735        length: 4,
736        styledKey: StyledStringKey.LINE_HEIGHT,
737        styledValue: this.lineHeightStyle1
738      }
739    ]);
740    paragraphStyledString2: MutableStyledString = new MutableStyledString("\n¥16.21 3000+人好评", [
741      {
742        start: 0,
743        length: 5,
744        styledKey: StyledStringKey.PARAGRAPH_STYLE,
745        styledValue: this.leadingMarginValue
746      },
747      {
748        start: 0,
749        length: 4,
750        styledKey: StyledStringKey.LINE_HEIGHT,
751        styledValue: new LineHeightStyle(new LengthMetrics(60))
752      },
753      {
754        start: 0,
755        length: 7,
756        styledKey: StyledStringKey.FONT,
757        styledValue: this.boldTextStyle
758      },
759      {
760        start: 1,
761        length: 1,
762        styledKey: StyledStringKey.FONT,
763        styledValue: new TextStyle({ fontSize: LengthMetrics.vp(18) })
764      },
765      {
766        start: 2,
767        length: 2,
768        styledKey: StyledStringKey.FONT,
769        styledValue: new TextStyle({ fontSize: LengthMetrics.vp(36) })
770      },
771      {
772        start: 4,
773        length: 3,
774        styledKey: StyledStringKey.FONT,
775        styledValue: new TextStyle({ fontSize: LengthMetrics.vp(20) })
776      },
777      {
778        start: 7,
779        length: 9,
780        styledKey: StyledStringKey.FONT,
781        styledValue: new TextStyle({ fontColor: Color.Grey, fontSize: LengthMetrics.vp(14)})
782      }
783    ]);
784
785    build() {
786      Row() {
787        Column({ space: 10 }) {
788          Text(undefined, { controller: this.controller })
789            .copyOption(CopyOptions.InApp)
790            .draggable(true)
791            .backgroundColor('#FFFFFF')
792            .borderRadius(5)
793
794          Button('点击查看商品卡片')
795            .onClick(() => {
796              if (this.imagePixelMap !== undefined) {
797                this.mutableStr = new MutableStyledString(new ImageAttachment({
798                  value: this.imagePixelMap,
799                  size: { width: 180, height: 160 },
800                  verticalAlign: ImageSpanAlignment.BASELINE,
801                  objectFit: ImageFit.Fill
802                }));
803                this.paragraphStyledString1.appendStyledString(this.paragraphStyledString2);
804                this.mutableStr.appendStyledString(this.paragraphStyledString1);
805                this.controller.setStyledString(this.mutableStr);
806              }
807            })
808        }
809        .width('100%')
810      }
811      .height('100%')
812      .backgroundColor('#F8F8FF')
813    }
814  }
815  ```
816  ![StyledString_ImageAttachment](figures/StyledStringImageAttachment.png)
817
818## 设置事件
819
820可通过[GestureStyle](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#gesturestyle)设置onClick、onLongPress事件来使文本响应点击长按事件。
821
822除了初始化属性字符串对象即初始样式对象,亦可通过[setStyle](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#setstyle)接口再叠加新样式或更新已有样式,同时需要在附加的文本组件controller上主动触发更新绑定的属性字符串。
823
824  ```ts
825  import { drawing } from '@kit.ArkGraphics2D';
826
827  let gUIContext: UIContext;
828
829  class MyCustomSpan extends CustomSpan {
830    constructor(word: string, width: number, height: number, fontSize: number) {
831      super();
832      this.word = word;
833      this.width = width;
834      this.height = height;
835      this.fontSize = fontSize;
836    }
837
838    onMeasure(measureInfo: CustomSpanMeasureInfo): CustomSpanMetrics {
839      return { width: this.width, height: this.height };
840    }
841
842    onDraw(context: DrawContext, options: CustomSpanDrawInfo) {
843      let canvas = context.canvas;
844
845      const brush = new drawing.Brush();
846      brush.setColor({
847        alpha: 255,
848        red: 0,
849        green: 0,
850        blue: 0
851      });
852      const font = new drawing.Font();
853      font.setSize(gUIContext.vp2px(this.fontSize));
854      const textBlob =
855        drawing.TextBlob.makeFromString(this.word.substring(0, 5), font, drawing.TextEncoding.TEXT_ENCODING_UTF8);
856      canvas.attachBrush(brush);
857
858      this.onDrawRectByRadius(context, options.x, options.x + gUIContext.vp2px(this.width), options.lineTop,
859        options.lineBottom, 20);
860      brush.setColor({
861        alpha: 255,
862        red: 255,
863        green: 255,
864        blue: 255
865      });
866      canvas.attachBrush(brush);
867      canvas.drawTextBlob(textBlob, options.x, options.baseline);
868      brush.setColor({
869        alpha: 255,
870        red: 255,
871        green: 228,
872        blue: 196
873      });
874      canvas.attachBrush(brush);
875      const textBlob1 =
876        drawing.TextBlob.makeFromString(this.word.substring(5), font, drawing.TextEncoding.TEXT_ENCODING_UTF8);
877      canvas.drawTextBlob(textBlob1, options.x + gUIContext.vp2px(100), options.baseline);
878
879      canvas.detachBrush();
880    }
881
882    onDrawRectByRadius(context: DrawContext, left: number, right: number, top: number, bottom: number, radius: number) {
883      let canvas = context.canvas;
884      let path = new drawing.Path();
885
886      // 画带radius的rect
887      path.moveTo(left + radius, top);
888      path.lineTo(right - radius, top);
889      path.arcTo(right - 2 * radius, top, right, top + 2 * radius, 270, 90);
890      path.lineTo(right, bottom - radius);
891      path.arcTo(right - 2 * radius, bottom - 2 * radius, right, bottom, 0, 90);
892
893      path.lineTo(left + 2 * radius, bottom);
894      path.arcTo(left, bottom - 2 * radius, left + 2 * radius, bottom, 90, 90);
895      path.lineTo(left, top + 2 * radius);
896      path.arcTo(left, top, left + 2 * radius, top + 2 * radius, 180, 90);
897
898      canvas.drawPath(path);
899    }
900
901    setWord(word: string) {
902      this.word = word;
903    }
904
905    width: number = 160;
906    word: string = "drawing";
907    height: number = 10;
908    fontSize: number = 16;
909  }
910
911  @Entry
912  @Component
913  struct styled_string_demo6 {
914    customSpan3: MyCustomSpan = new MyCustomSpan("99VIP88%off", 200, 40, 30);
915    customSpanStyledString: MutableStyledString = new MutableStyledString(this.customSpan3);
916    textController: TextController = new TextController();
917    isPageShow: boolean = true;
918    @State backgroundColor1: ResourceColor | undefined = undefined;
919    gestureStyleAttr: GestureStyle = new GestureStyle({
920      onClick: () => {
921        this.backgroundColor1 = Color.Green;
922      },
923      onLongPress: () => {
924        this.backgroundColor1 = Color.Grey;
925      }
926    });
927
928    aboutToAppear() {
929      gUIContext = this.getUIContext();
930    }
931
932    async onPageShow() {
933      if (!this.isPageShow) {
934        return;
935      }
936      this.isPageShow = false;
937      this.customSpanStyledString.setStyle({
938        start: 0,
939        length: 1,
940        styledKey: StyledStringKey.GESTURE,
941        styledValue: this.gestureStyleAttr
942      })
943      this.textController.setStyledString(this.customSpanStyledString);
944    }
945
946    build() {
947      Row() {
948        Column() {
949          Button("响应属性字符串事件改变背景色").backgroundColor(this.backgroundColor1).width('80%').margin(10)
950          Text(undefined, { controller: this.textController })
951            .copyOption(CopyOptions.InApp)
952            .fontSize(30)
953        }
954        .width('100%')
955      }
956      .height('100%')
957    }
958  }
959  ```
960  ![styled_string_event](figures/styled_string_event.gif)
961
962## 格式转换
963
964可以通过[toHtml](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#tohtml14)、[fromHtml](../reference/apis-arkui/arkui-ts/ts-universal-styled-string.md#fromhtml)接口实现属性字符串与HTML格式字符串的相关转换,当前支持转换的HTML标签范围:\<p>、\<span>、\<img>、\<br>、\<strong>、\<b>、\<a>、\<i>、\<em>、\<s>、\<u>、\<del>、\<sup>、\<sub>。
965
966- 以下示例展示了如何将属性字符串转换成HTML格式,并展示了如何从HTML格式转换回属性字符串。
967```ts
968// xxx.ets
969import { image } from '@kit.ImageKit';
970import { LengthMetrics } from '@kit.ArkUI';
971
972@Entry
973@Component
974struct styled_string_demo8 {
975  imagePixelMap: image.PixelMap | undefined = undefined;
976  @State html: string | undefined = undefined;
977  @State styledString: StyledString | undefined = undefined;
978  controller1: TextController = new TextController;
979  controller2: TextController = new TextController;
980  private uiContext: UIContext = this.getUIContext();
981
982  async aboutToAppear() {
983    console.info("aboutToAppear initial imagePixelMap");
984    // $r('app.media.startIcon')需要替换为开发者所需的图像资源文件。
985    this.imagePixelMap = await this.getPixmapFromMedia($r('app.media.startIcon'));
986  }
987
988  private async getPixmapFromMedia(resource: Resource) {
989    let unit8Array = await this.uiContext.getHostContext()?.resourceManager?.getMediaContent(resource.id);
990    let imageSource = image.createImageSource(unit8Array?.buffer.slice(0, unit8Array.buffer.byteLength));
991    let createPixelMap: image.PixelMap = await imageSource.createPixelMap({
992      desiredPixelFormat: image.PixelMapFormat.RGBA_8888
993    });
994    await imageSource.release();
995    return createPixelMap;
996  }
997
998  build() {
999    Column() {
1000      Text(undefined, { controller: this.controller1 }).height(100)
1001      Row() {
1002        Button("添加属性字符串").onClick(() => {
1003          let mutableStyledString1: MutableStyledString = new MutableStyledString("属性字符串", [{
1004            start: 0,
1005            length: 6,
1006            styledKey: StyledStringKey.FONT,
1007            styledValue: new TextStyle({ fontColor: Color.Green, fontSize: LengthMetrics.px(50) })
1008          }]);
1009          if (this.imagePixelMap !== undefined) {
1010            let mutableStyledString2 = new MutableStyledString(new ImageAttachment({
1011              value: this.imagePixelMap,
1012              size: { width: 50, height: 50 },
1013            }));
1014            mutableStyledString1.appendStyledString(mutableStyledString2);
1015          }
1016          this.styledString = mutableStyledString1;
1017          this.controller1.setStyledString(mutableStyledString1);
1018        }).margin(5)
1019        Button("toHtml").onClick(() => {
1020          this.html = StyledString.toHtml(this.styledString);
1021        }).margin(5)
1022        Button("fromHtml").onClick(async () => {
1023          let styledString = await StyledString.fromHtml(this.html);
1024          this.controller2.setStyledString(styledString);
1025        }).margin(5)
1026      }
1027
1028      Text(undefined, { controller: this.controller2 }).height(100)
1029      Text(this.html)
1030    }.width("100%")
1031  }
1032}
1033```
1034
1035![](figures/styled_string_html.gif)
1036
1037- 将HTML中\<strong>、\<b>、\<a>、\<i>、\<em>、\<s>、\<u>、\<del>、\<sup>、\<sub>标签及其style属性中的background-color转换为属性字符串并转回HTML。
1038  ```ts
1039  // xxx.ets
1040  @Entry
1041  @Component
1042  struct HtmlSpanStringDemo {
1043    @State html: string =
1044      "<p>This is <b>b</b> <strong>strong</strong> <em>em</em> <i>i</i> <u>u</u> <del>del</del> <s>s</s> <span style =   \"foreground-color:blue\"> <a href='https://www.example.com'>www.example</a> </span> <span   style=\"background-color: red;\">red span</span> <sup>superscript</sup> and <sub>subscript</sub></p>";
1045    @State spanString: StyledString | undefined = undefined;
1046    @State resultText: string = ""; // 保存结果文本的状态
1047    controller: TextController = new TextController;
1048
1049    build() {
1050      Column() {
1051        // 显示转换后的spanString
1052        Text(undefined, { controller: this.controller }).height(100)
1053
1054        // TextArea显示每个步骤的结果
1055        TextArea({ text: this.html })
1056          .width("100%")
1057          .height(100)
1058          .margin(5)
1059
1060        // 按钮1:将HTML转换为SpanString
1061        Button("Converted HTML to SpanString").onClick(async () => {
1062          this.spanString = await StyledString.fromHtml(this.html);
1063          this.controller.setStyledString(this.spanString);
1064          this.resultText = "Converted HTML to SpanString successfully.";
1065        }).margin(5)
1066
1067        // 按钮2:将SpanString转换为HTML
1068        Button("Converted SpanString to HTML").onClick(() => {
1069          if (this.spanString) {
1070            // 将spanString转换为HTML并替换当前的HTML状态
1071            const newHtml = StyledString.toHtml(this.spanString);
1072            if (newHtml !== this.html) { // 通过检查内容是否已经相同来防止重复
1073              this.html = newHtml;
1074            }
1075            this.resultText = "Converted SpanString to HTML successfully.";
1076          } else {
1077            this.resultText = "SpanString is undefined.";
1078          }
1079        }).margin(5)
1080
1081        // 按钮3:将HTML转换回SpanString
1082        Button("Converted HTML back to SpanString").onClick(async () => {
1083          this.spanString = await StyledString.fromHtml(this.html);
1084          this.controller.setStyledString(this.spanString);
1085          this.resultText = "Converted HTML back to SpanString successfully.";
1086        }).margin(5)
1087
1088        // 重置:重置HTML和SpanString
1089        Button("Reset").onClick(() => {
1090          this.html =
1091            "<p>This is <b>b</b> <strong>strong</strong> <em>em</em> <i>i</i> <u>u</u> <del>del</del> <s>s</s> <span   style = \"foreground-color:blue\"> <a href='https://www.example.com'>www.example</a> </span> <span   style=\"background-color: red;\">red span</span> <sup>superscript</sup> and <sub>subscript</sub></p>";
1092          this.spanString = undefined;
1093          this.controller.setStyledString(new StyledString("")); // 使用空的StyledString实例
1094          this.resultText = "Reset HTML and SpanString successfully.";
1095        }).margin(5)
1096      }.width("100%").padding(20)
1097    }
1098  }
1099  ```
1100
1101  ![styled_string_html_2](figures/styled_string_html_2.gif)
1102
1103## 场景示例
1104
1105该示例通过ParagraphStyle、LineHeightStyle、TextStyle对象展示了会员过期提示的效果。
1106
1107```ts
1108import { LengthMetrics } from '@kit.ArkUI';
1109
1110@Entry
1111@Component
1112struct Index {
1113  alignCenterParagraphStyleAttr: ParagraphStyle = new ParagraphStyle({ textAlign: TextAlign.Center });
1114  // 行高样式对象
1115  lineHeightStyle1: LineHeightStyle = new LineHeightStyle(LengthMetrics.vp(24));
1116  // Bold样式
1117  boldTextStyle: TextStyle = new TextStyle({ fontWeight: FontWeight.Bold });
1118  // 创建含段落样式的对象paragraphStyledString1
1119  paragraphStyledString1: MutableStyledString =
1120    new MutableStyledString("您的豪华钻石已过期1天\n续费可继续享受会员专属权益", [
1121      {
1122        start: 0,
1123        length: 4,
1124        styledKey: StyledStringKey.PARAGRAPH_STYLE,
1125        styledValue: this.alignCenterParagraphStyleAttr
1126      },
1127      {
1128        start: 0,
1129        length: 4,
1130        styledKey: StyledStringKey.LINE_HEIGHT,
1131        styledValue: new LineHeightStyle(LengthMetrics.vp(40))
1132      },
1133      {
1134        start: 11,
1135        length: 14,
1136        styledKey: StyledStringKey.FONT,
1137        styledValue: new TextStyle({ fontSize: LengthMetrics.vp(14), fontColor: Color.Grey })
1138      },
1139      {
1140        start: 11,
1141        length: 4,
1142        styledKey: StyledStringKey.PARAGRAPH_STYLE,
1143        styledValue: this.alignCenterParagraphStyleAttr
1144      },
1145      {
1146        start: 11,
1147        length: 4,
1148        styledKey: StyledStringKey.LINE_HEIGHT,
1149        styledValue: this.lineHeightStyle1
1150      }
1151    ]);
1152  paragraphStyledString2: MutableStyledString = new MutableStyledString("\n¥4.88¥15", [
1153    {
1154      start: 0,
1155      length: 4,
1156      styledKey: StyledStringKey.PARAGRAPH_STYLE,
1157      styledValue: this.alignCenterParagraphStyleAttr
1158    },
1159    {
1160      start: 0,
1161      length: 4,
1162      styledKey: StyledStringKey.LINE_HEIGHT,
1163      styledValue: new LineHeightStyle(LengthMetrics.vp(60))
1164    },
1165    {
1166      start: 0,
1167      length: 6,
1168      styledKey: StyledStringKey.FONT,
1169      styledValue: this.boldTextStyle
1170    },
1171    {
1172      start: 1,
1173      length: 1,
1174      styledKey: StyledStringKey.FONT,
1175      styledValue: new TextStyle({ fontSize: LengthMetrics.vp(18) })
1176    },
1177    {
1178      start: 2,
1179      length: 4,
1180      styledKey: StyledStringKey.FONT,
1181      styledValue: new TextStyle({ fontSize: LengthMetrics.vp(40) })
1182    },
1183    {
1184      start: 6,
1185      length: 3,
1186      styledKey: StyledStringKey.FONT,
1187      styledValue: new TextStyle({ fontColor: Color.Grey, fontSize: LengthMetrics.vp(14) })
1188    },
1189    {
1190      start: 6,
1191      length: 3,
1192      styledKey: StyledStringKey.DECORATION,
1193      styledValue: new DecorationStyle({ type: TextDecorationType.LineThrough, color: Color.Grey })
1194    }
1195  ]);
1196  paragraphStyledString3: MutableStyledString = new MutableStyledString("\n02时06分后将失去该优惠", [
1197    {
1198      start: 0,
1199      length: 4,
1200      styledKey: StyledStringKey.PARAGRAPH_STYLE,
1201      styledValue: this.alignCenterParagraphStyleAttr
1202    },
1203    {
1204      start: 0,
1205      length: 4,
1206      styledKey: StyledStringKey.LINE_HEIGHT,
1207      styledValue: new LineHeightStyle(LengthMetrics.vp(30))
1208    },
1209    {
1210      start: 1,
1211      length: 2,
1212      styledKey: StyledStringKey.FONT,
1213      styledValue: new TextStyle({ fontColor: '#FFD700', fontWeight: FontWeight.Bold })
1214    },
1215    {
1216      start: 4,
1217      length: 2,
1218      styledKey: StyledStringKey.FONT,
1219      styledValue: new TextStyle({ fontColor: '#FFD700', fontWeight: FontWeight.Bold })
1220    }
1221  ]);
1222  controller: TextController = new TextController();
1223
1224  build() {
1225    Row() {
1226      Column({ space: 5 }) {
1227        Text(undefined, { controller: this.controller })
1228          .width(240)
1229          .copyOption(CopyOptions.InApp)
1230          .draggable(true)
1231          .onAppear(() => {
1232            this.paragraphStyledString2.appendStyledString(this.paragraphStyledString3);
1233            this.paragraphStyledString1.appendStyledString(this.paragraphStyledString2);
1234            this.controller.setStyledString(this.paragraphStyledString1);
1235          })
1236
1237        Button("限时4.88元 立即续费")
1238          .width(200)
1239          .fontColor(Color.White)
1240          .fontSize(18)
1241          .backgroundColor('#3CB371')
1242          .margin({ bottom: 10 })
1243      }
1244      .borderWidth(1).borderColor('#FFDEAD')
1245      .margin({ left: 10 })
1246    }
1247    .height('60%')
1248  }
1249}
1250```
1251![StyledString_SceneDemo](figures/styledString_sceneDemo.png)
1252