DEV Community

HarmonyOS
HarmonyOS

Posted on

Implement placeholder text that scrolls up with the content

Read the original article:Implement placeholder text that scrolls up with the content

Problem Description

Set a placeholder text. How to make the placeholder text undeletable and automatically scroll upward as input data increases, as shown below:

cke_1089.gif

Background Knowledge

  • Stack is a stacking container where child components are stacked in order, with the latter child components overlaying the previous ones.
  • TextArea is a multi-line text input component that automatically wraps text when content exceeds the component width.
  • onContentScroll callback is triggered when the TextArea content scrolls, which can be used to make the placeholder text scroll upward with the content.
  • onWillDelete callback is triggered when the TextArea is about to delete content, which can be used to make the placeholder text undeletable.

Solution

1.Use the Stack component to combine placeholder text and the text input box.

   Stack({ alignContent: Alignment.TopStart }) {
     Column() {
       Text(this.txt)
       // ...
     }

     TextArea({ text: this.text, controller: this.controller })
     // ...
   }
Enter fullscreen mode Exit fullscreen mode

2.In the onContentScroll callback, modify the offsetY property to change the relative vertical offset of the placeholder text, achieving automatic upward scrolling.

   .onContentScroll((totalOffsetX: number, totalOffsetY: number) => {
     if ((this.getUIContext().px2vp(totalOffsetY) - 8) > 0) {
       this.offsetY = 0
     } else if ((this.getUIContext().px2vp(totalOffsetY) - 8) < -16) {
       this.offsetY = -16
     } else {
       this.offsetY = (this.getUIContext().px2vp(totalOffsetY) - 8)
     }
   })
Enter fullscreen mode Exit fullscreen mode

3.In the onWillDelete callback, determine whether the cursor can move backward based on deleteOffset, making the placeholder text undeletable.

   .onWillDelete((info: DeleteValue) => {
     if (info.deleteOffset === 1 || this.text === ' ') {
       return false
     }
     return true;
   })
Enter fullscreen mode Exit fullscreen mode

Complete example is as follows:

@Entry
@Component
struct Index {
  @State txt: string = 'Placeholder Text'
  @State text: string = ' '
  @State textValue: string = ''
  @State txtWidth: number = 0
  @State offsetY: number = 0
  controller: TextAreaController = new TextAreaController()

  build() {
    Row() {
      Column() {
        Stack({ alignContent: Alignment.TopStart }) {
          Column() {
            Text(this.txt)
              .margin({ left: 14 })
              .fontSize(15)
              .fontColor(Color.Blue)
              .onAreaChange((oldValue: Area, newValue: Area) => {
                this.txtWidth = newValue.width as number
              })
              .offset({ x: 0, y: this.offsetY })
          }
          .width(200)
          .height(92)
          .background(Color.White)
          .alignItems(HorizontalAlign.Start)
          .border({ width: 1 })
          .margin({ top: 8 })
          .clip(true)

          TextArea({ text: this.text, controller: this.controller })
            .width(200)
            .height(100)
            .wordBreak(WordBreak.BREAK_ALL)
            .textIndent(this.txtWidth)
            .borderRadius(0)
            .fontColor(Color.Black)
            .onChange((info) => {
              this.text = info
              this.textValue = this.text.trim()
            })
            .backgroundColor(Color.Transparent)
            .onContentScroll((totalOffsetX: number, totalOffsetY: number) => {
              if ((this.getUIContext().px2vp(totalOffsetY) - 8) > 0) {
                this.offsetY = 0
              } else if ((this.getUIContext().px2vp(totalOffsetY) - 8) < -16) {
                this.offsetY = -16
              } else {
                this.offsetY = (this.getUIContext().px2vp(totalOffsetY) - 8)
              }
            })
            .onWillDelete((info: DeleteValue) => {
              if (info.deleteOffset === 1 || this.text === ' ') {
                return false
              }
              return true;
            })
            .onDidDelete((info: DeleteValue) => {
              if (info.deleteOffset === 0) {
                this.text = ' '
              }
            })
            .onTextSelectionChange((selectionStart: number, selectionEnd: number) => {
              if (selectionStart === 0) {
                selectionStart === selectionEnd ? this.controller.caretPosition(1) :
                  this.controller.setTextSelection(1, selectionEnd)
              }
            })
        }
      }
      .width('100%')
    }
    .height('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

Verification Result

img

Written by Bunyamin Akcay

Top comments (0)