# 行内文本高亮样式设置方案

#### 问题现象

在对文本进行编辑时有些文字需要高亮显示，如搜索框内搜索内容、关键词提示、歌词播放等场景，如何实现该功能呢？

#### 效果预览

#### 背景知识

在开发文本展示和编辑功能时，我们可以使用几个关键组件、接口来增强用户体验。以下是这些组件和接口的功能概述及其使用方式：

*   Span组件是Text子组件，主要用于显示行内文本，并允许为不同的文字部分设置不同的属性，如颜色、字体大小等，从而实现高亮或其他视觉效果。
*   AttributeModifier是属性修改器接口，提供了applyXxxAttribute方法，让开发者能够根据具体需求调整文本属性。
*   MutableStyledString是方便灵活应用文本样式的对象。可以通过TextController中的setStyledString方法与Text组件绑定，实现动态地改变文本样式。
*   RichEditor组件支持图文混排和文本交互编辑，可以实时更新文本样式，实现搜索匹配内容的高亮样式。适合需要编辑的场景，其他场景不推荐。

#### 解决方案

行内部分文本高亮样式设置可以通过Span组件、AttributeModifier属性修改器、MutableStyledString属性字符串、RichEditor组件等实现。

*   Span组件和AttributeModifier属性修改器方案主要是基于Text组件嵌套Span子组件，通过Span组件对需要高亮显示的文本单独设置高亮样式。
*   MutableStyledString属性字符串可以为指定范围的文字设置指定样式，Text组件和RichEditor组件均可通过setStyledString方法设置MutableStyledString属性。
*   RichEditor组件可以通过控制器RichEditorController的addTextSpan方法添加文本内容，并设置文本内容的样式。

具体有以下四个实现方案：

*   **方案一**：Text组件将匹配的字符串通过Span设置高亮样式。
    
    ```
    @Entry
    @Component
    struct OptionOne {
      searchValue: string = '';
      dataArr: string[] = [
        '南窗枕书卧，醉眼看山横',
        '海棠花下思朦胧，醉春风',
        '醉里挑灯看剑，梦回吹角连营',
        '醉后不知天在水，满船清梦压星河',
        '指洞庭为酒，渴时浩饮，君山作枕，醉后高眠'
      ];
      @State dataArr2: string[] = [];
    
      build() {
        Row() {
          Column() {
            Search({ value: $$this.searchValue, placeholder: 'Type to search...' })
              .width('90%')
              .onChange(() => {
                let dataArr3: string[] = [];
                this.dataArr.filter((item: string) => {
                  let index = item.indexOf(this.searchValue);
                  if (this.searchValue && index != -1) {
                    dataArr3.push(item);
                  }
                });
                this.dataArr2 = dataArr3;
              });
            Column() {
              ForEach(this.dataArr2, (item: string) => {
                Row() {
                  // 多关键字匹配
                  Text() {
                    Span(item.substring(0, item.indexOf(this.searchValue)));
                    // 青色高亮文字
                    Span(this.searchValue).fontColor('#87ceeb');
                    Span(item.substring(item.indexOf(this.searchValue) + this.searchValue.length, item.length - 1));
                    // 最后一个文字显示为蓝色
                    Span(item.substring(item.length - 1)).fontColor(Color.Blue);
                  };
                }
                .width('100%')
                .height(40)
                .justifyContent(FlexAlign.Center);
              });
            };
          }
          .width('100%');
        }
        .height('100%');
      }
    }
    ```
    
*   **方案二**：AttributeModifier属性修改器通过applyNormalAttribute方法对Span类型组件回调，获取其属性对象并设置高亮样式。
    
    ```
    @Entry
    @Component
    struct OptionTwo {
      searchValue: string = '';
      dataArr: string[] = [
        '南窗枕书卧，醉眼看山横',
        '海棠花下思朦胧，醉春风',
        '醉里挑灯看剑，梦回吹角连营',
        '醉后不知天在水，满船清梦压星河',
        '指洞庭为酒，渴时浩饮，君山作枕，醉后高眠'
      ];
      @State dataArr2: string[] = [];
    
      build() {
        Row() {
          Column() {
            Search({ value: $$this.searchValue, placeholder: 'Type to search...' })
              .width('90%')
              .onChange(() => {
                let dataArr3: string[] = [];
                this.dataArr.filter((item: string) => {
                  let index = item.indexOf(this.searchValue);
                  if (this.searchValue && index != -1) {
                    dataArr3.push(item);
                  }
                });
                this.dataArr2 = dataArr3;
              });
            Column() {
              ForEach(this.dataArr2, (item: string) => {
                Row() {
                  // 多关键字匹配
                  Text() {
                    Span(item.substring(0, item.indexOf(this.searchValue))).attributeModifier(new HighlightModifier(false));
                    // 青色高亮文字
                    Span(this.searchValue).attributeModifier(new HighlightModifier(true));
                    Span(item.substring(item.indexOf(this.searchValue) + this.searchValue.length, item.length - 1))
                      .attributeModifier(new HighlightModifier(false));
                    // 最后一个文字显示为蓝色
                    Span(item.substring(item.length - 1))
                      .fontColor(Color.Blue);
                  };
                }
                .width('100%')
                .height(40)
                .justifyContent(FlexAlign.Center);
              });
            };
          }
          .width('100%');
        }
        .height('100%');
      }
    }
    
    export class HighlightModifier implements AttributeModifier<SpanAttribute> {
      isHighlight: boolean = false;
    
      constructor(isHighlight: boolean) {
        this.isHighlight = isHighlight;
      }
    
      applyNormalAttribute(instance: SpanAttribute): void {
        instance.fontColor(this.isHighlight ? '#87ceeb' : Color.Black).fontSize(15);
      }
    }
    ```
    
*   **方案三**：使用属性字符串MutableStyledString给字符串添加高亮样式，然后通过Text组件的setStyledString方法设置其展示。
    
    ```
    import { cryptoFramework } from '@kit.CryptoArchitectureKit';
    
    @Entry
    @Component
    struct OptionFour {
      value: string = '';
      dataArr: string[] = [
        '南窗枕书卧，醉眼看山横',
        '海棠花下思朦胧，醉春风',
        '醉里挑灯看剑，梦回吹角连营',
        '醉后不知天在水，满船清梦压星河',
        '指洞庭为酒，渴时浩饮，君山作枕，醉后高眠'
      ];
      @State showArr: TmpFour[] = [];
      Style1: TextStyle = new TextStyle({ fontColor: '#87ceeb' });
      Style2: TextStyle = new TextStyle({ fontColor: Color.Blue });
    
      build() {
        Row() {
          Column() {
            Search({ value: $$this.value, placeholder: 'Type to search...' })
              .width('90%')
              .onChange(() => {
                this.showArr = [];
                this.dataArr.filter((item: string) => {
                  let index = item.indexOf(this.value);
                  // 匹配关键词，并设置对应Style样式
                  if (this.value && index != -1) {
                    let mutableStyledString = new MutableStyledString(item, [
                      {
                        start: index,
                        length: this.value.length,
                        styledKey: StyledStringKey.FONT,
                        styledValue: this.Style1
                      },
                      {
                        start: item.length - 1,
                        length: 1,
                        styledKey: StyledStringKey.FONT,
                        styledValue: this.Style2
                      }]);
                    this.showArr.push(new TmpFour(mutableStyledString, new TextController()));
                  }
                });
              });
            ForEach(this.showArr, (item: TmpFour) => {
              Row() {
                Text(undefined, { controller: item.controller })
                  .onAppear(() => {
                    // 通过setStyledString方法，添加对应文本样式
                    item.controller.setStyledString(item.style);
                  });
              }.width('100%').height(40).justifyContent(FlexAlign.Center);
            }, (item: TmpFour, index: number) => {
              return index + '__' + item.style.getString() + JSON.stringify(cryptoFramework.createRandom().generateRandom(10));
            });
          }
          .width('100%')
          .height('100%')
          .padding(16)
          .justifyContent(FlexAlign.Center);
        };
      }
    }
    
    class TmpFour {
      style: MutableStyledString;
      controller: TextController;
    
      constructor(style: MutableStyledString, controller: TextController) {
        this.style = style;
        this.controller = controller;
      }
    }
    ```
    
*   **方案四**：RichEditor组件获取匹配的字符串列表，然后通过addTextSpan方法对已匹配文本设置高亮样式。
    
    ```
    class TmpThree {
      controller: RichEditorController;
      value: string;
    
      constructor(controller: RichEditorController, value: string) {
        this.controller = controller;
        this.value = value;
      }
    }
    
    @Entry
    @Component
    struct OptionThree {
      searchValue: string = '';
      dataArr: string[] = [
        '南窗枕书卧，醉眼看山横',
        '海棠花下思朦胧，醉春风',
        '醉里挑灯看剑，梦回吹角连营',
        '醉后不知天在水，满船清梦压星河',
        '指洞庭为酒，渴时浩饮，君山作枕，醉后高眠'
      ];
      @State dataArr2: TmpThree[] = [];
    
      // 设置空键盘
      @Builder
      TestBoard() {
      }
    
      build() {
        Row() {
          Column() {
            Search({ value: $$this.searchValue, placeholder: 'Type to search...' })
              .width('90%')
              .onChange(() => {
                let dataArr3: TmpThree[] = [];
                this.dataArr.filter((item: string) => {
                  let index = item.indexOf(this.searchValue);
                  if (this.searchValue && index != -1) {
                    let controller: RichEditorController = new RichEditorController();
                    let temp: TmpThree = new TmpThree(controller, item);
                    dataArr3.push(temp);
                  }
                });
                this.dataArr2 = dataArr3;
              });
            ForEach(this.dataArr2, (item: TmpThree) => {
              Row() {
                RichEditor({ controller: item.controller })
                  .customKeyboard(this.TestBoard()) // 绑定空键盘，禁止编辑。
                  .onReady(() => {
                    // 添加高亮显示的属性
                    item.controller.addTextSpan(item.value.substring(0, item.value.indexOf(this.searchValue)),
                      {
                        style: { fontColor: Color.Black, fontSize: 16 }
                      });
                    item.controller.addTextSpan(this.searchValue,
                      {
                        style: { fontColor: '#87ceeb', fontSize: 16 }
                      });
                    item.controller.addTextSpan(item.value.substring(item.value.indexOf(this.searchValue) +
                    this.searchValue.length,
                      item.value.length - 1),
                      {
                        style: { fontColor: Color.Black, fontSize: 16 }
                      });
                    item.controller.addTextSpan(item.value.substring(item.value.length - 1),
                      {
                        style:
                        {
                          fontColor: Color.Blue,
                          fontSize: 16
                        }
                      });
                  })
              }.width('100%').height(40).justifyContent(FlexAlign.Center);
            });
          }
          .width('100%');
        }
        .height('100%');
      }
    }
    ```