DEV Community

HarmonyOS
HarmonyOS

Posted on

Inline Text Highlighting Style Implementation

Read the original article:Inline Text Highlighting Style Implementation

Requirement Description

In scenarios such as text editing, search boxes, keyword prompts, or lyrics display, certain words need to be highlighted. How can this functionality be implemented in HarmonyOS ArkUI?

Background Knowledge

When developing text display and editing features, several key components and APIs can be used to enhance the user experience:

  • Span Component: A subcomponent of Text, used for inline text with customizable attributes.
  • AttributeModifier: Provides methods to modify attributes dynamically.
  • RichEditor: Supports mixed text and graphics editing with real-time style updates.
  • MutableStyledString: Allows flexible text styling bound to Text via TextController.setStyledString.

Implementation Steps

You should use on of these steps:

  • Use span in Text Component
  • Attribute Modifier with Span
  • RichEditor with addTextSpan
  • MutableStyledString with TextController

Code Snippet / Configuration

Solution 1: Using Span in Text Component

@Entry
@Component
struct Page {
  @State searchValue: string = 'drunk';
  @State dataArr: string[] = [
    'Resting by the window, drunk eyes gazing at the mountains',
    'Under the begonia flowers, lost in the spring breeze',
    'Drunk, I light the lamp and look at the sword, dreaming of the battlefield',
    'After being drunk, not knowing the sky is in the water, a boat full of dreams pressed by the stars',
    'Pointing to Dongting as wine, drinking deeply when thirsty, Mount Jun as a pillow, sleeping soundly after drunk'
  ]

  build() {
    Row() {
      Column() {
        ForEach(this.dataArr, (item: string) => {
          Row() {
            Text() {
              Span(item.substring(0, item.indexOf(this.searchValue)))
              // Highlighted word in red
              Span(this.searchValue).fontColor(Color.Red)
              Span(item.substring(item.indexOf(this.searchValue) + this.searchValue.length, item.length - 1))
              // Last character in blue
              Span(item.substring(item.length - 1)).fontColor(Color.Blue)
            }
          }
          .width('100%')
          .height(40)
          .justifyContent(FlexAlign.Center)
        })
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Solution 2: AttributeModifier with Span

@Entry
@Component
struct Index {
  @State searchValue: string = 'drunk';
  @State dataArr: string[] = [
    'Resting by the window, drunk eyes gazing at the mountains',
    'Under the begonia flowers, lost in the spring breeze',
    'Drunk, I light the lamp and look at the sword, dreaming of the battlefield',
    'After being drunk, not knowing the sky is in the water, a boat full of dreams pressed by the stars',
    'Pointing to Dongting as wine, drinking deeply when thirsty, Mount Jun as a pillow, sleeping soundly after drunk'
  ]

  build() {
    Column() {
      ForEach(this.dataArr, (item: string) => {
        Row() {
          Text() {
            // Multiple keyword matching
            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))
          }
        }
        .height(40)
        .justifyContent(FlexAlign.Center)
      })
    }.justifyContent(FlexAlign.Center)
  }
}

export class HighlightModifier implements AttributeModifier<SpanAttribute> {
  isHighlight: boolean = false

  constructor(isHighlight: boolean) {
    this.isHighlight = isHighlight
  }

  applyNormalAttribute(instance: SpanAttribute): void {
    instance.fontColor(this.isHighlight ? Color.Red : Color.Black).fontSize(15)
  }
}
Enter fullscreen mode Exit fullscreen mode

Solution 3: RichEditor with addTextSpan

@Entry
@Component
struct Page {
  @State value: string = 'drunk';
  @State dataArr: Tmp[] = [
    new Tmp(new RichEditorController(), 'Resting by the window, drunk eyes gazing at the mountains'),
    new Tmp(new RichEditorController(), 'Under the begonia flowers, lost in the spring breeze'),
    new Tmp(new RichEditorController(), 'Drunk, I light the lamp and look at the sword, dreaming of the battlefield'),
    new Tmp(new RichEditorController(), 'After being drunk, not knowing the sky is in the water, a boat full of dreams pressed by the stars'),
    new Tmp(new RichEditorController(), 'Pointing to Dongting as wine, drinking deeply when thirsty, Mount Jun as a pillow, sleeping soundly after drunk'),
  ]

  build() {
    Column() {
      ForEach(this.dataArr, (item: Tmp) => {
        ListItem() {
          Row() {
            RichEditor({ controller: item.controller })
              .onReady(() => {
                // Add text span with normal style
                item.controller.addTextSpan(item.value.substring(0, item.value.indexOf(this.value)),
                  {
                    style: { fontColor: Color.Black, fontSize: 16 }
                  })
                // Add highlighted text span
                item.controller.addTextSpan(this.value,
                  {
                    style: { fontColor: Color.Red, fontSize: 16 }
                  })
                // Add remaining text span
                item.controller.addTextSpan(item.value.substring(item.value.indexOf(this.value) + this.value.length),
                  {
                    style: { fontColor: Color.Black, fontSize: 16 }
                  })
              })
          }.width('100%').height(40).justifyContent(FlexAlign.Center)
        }
      })
    }
  }
}

class Tmp {
  controller: RichEditorController
  value: string

  constructor(controller: RichEditorController, value: string) {
    this.controller = controller
    this.value = value
  }
}
Enter fullscreen mode Exit fullscreen mode

Solution 4: MutableStyledString with TextController

@Entry
@Component
struct Page {
  @State value: string = '';
  @State dataArr: string[] = [
    'Resting by the window, drunk eyes gazing at the mountains',
    'Under the begonia flowers, lost in the spring breeze',
    'Drunk, I light the lamp and look at the sword, dreaming of the battlefield',
    'After being drunk, not knowing the sky is in the water, a boat full of dreams pressed by the stars',
    'Pointing to Dongting as wine, drinking deeply when thirsty, Mount Jun as a pillow, sleeping soundly after drunk'
  ]
  @State showArr: Tmp[] = []
  Style1: TextStyle = new TextStyle({ fontColor: Color.Red });
  Style2: TextStyle = new TextStyle({ fontColor: Color.Blue });

  build() {
    Row() {
      Column() {
        Search({ value: $$this.value, placeholder: 'Type to search...' })
          .onChange(() => {
            this.showArr = []
            this.dataArr.filter((item: string) => {
              let index = item.indexOf(this.value)
              // Match keyword and apply corresponding 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 Tmp(mutableStyledString, new TextController()))
              }
            })
          })
        ForEach(this.showArr, (item: Tmp) => {
          Row() {
            Text(undefined, { controller: item.controller })
              .onAppear(() => {
                // Apply styled string with setStyledString
                item.controller.setStyledString(item.style)
              })
          }.width('100%').height(40).justifyContent(FlexAlign.Center)
        })
      }
    }
  }
}

class Tmp {
  style: MutableStyledString
  controller: TextController

  constructor(style: MutableStyledString, controller: TextController) {
    this.style = style
    this.controller = controller
  }
}
Enter fullscreen mode Exit fullscreen mode

Test Results

  • Solution 1 & 2: Simple inline highlighting in Text.
  • Solution 3 & 4: Richer, suited for complex text editing scenarios.

Limitations or Considerations

  • API Version 20 Release
  • HarmonyOS 6.0.0 Release SDK
  • DevEco Studio 6.0.0 Release

Written by Arif Emre Ankara

Top comments (0)