场景描述

场景一:英文、数字及特殊字符和中文字符自动间隔一个空格的距离场景二:当输入的内容(纯数字可能为小数)大于999时,禁止输入,注意最后一位光标不要闪烁场景三:限制两位小数,不能以小数点开头的场景场景四:TextInput如何在输入、粘贴、剪切动作之后,内容显示之前控制显示内容场景五:电话号码格式化时,删除号码中间的空格为删除前一位数字场景六:textInput防抖与节流

场景一:TextInput实现输入框热搜词自动滚动及右侧文字内容颜色渐变

效果图:

方案:

1、封装一个正则函数,英文数字及特殊字符用[a-zA-Z0-9\~!@\#$%^&\*\(\)\\-\_=+\\\[\\\]\{\}\\\\|;:'",<.\>/?\],中文用\[\\u4e00-\\u9fa5\], 在正则中$1 和 $2 是反向引用,它们用于引用正则表达式中捕获组匹配到的文本,捕获组是用圆括号 \(\) , 每个捕获组匹配到的文本可以在替换字符串中通过 $1 和 $2 等来引用。

// 场景1 英文、数字及特殊字符和中文字符自动间隔空格的距离
formatStringOne(value: string) {
  // 中文与英文或者特殊字符空格 /g全局
  // 每个捕获组匹配到的文本可以在替换字符串中通过 $1、$2 等来引用。
  let pattern = /([\u4e00-\u9fa5])([a-zA-Z0-9`~!@#$%^&*()\-_=+\[\]{}\\|;:'",<.>/?])/g;
  let result = value.replace(pattern, '$1 $2');
  let pattern1 = /([a-zA-Z0-9`~!@#$%^&*()\-_=+\[\]{}\\|;:'",<.>/?])([\u4e00-\u9fa5])/g;
  let result1 = result.replace(pattern1, '$1 $2');
  return result1;
}

2、在onChange回调中调用封装的正则匹配函数formatStringOne。

TextInput({ text: this.textOne, placeholder: 'input your word ...', controller: this.controllerOne })
  .onChange((value:string)=>{
    this.textOne = this.formatStringOne(value)
  })

场景二:当输入的内容(纯数字可能为小数)大于999时,禁止输入,注意最后一位光标不要闪烁

效果图:

方案:

1、onWillInsert是在将要输入时调用的回调。在返回true时,表示正常插入,返回false时,表示不插入。在预上屏操作时,该回调不触发。注意:仅支持系统输入法输入的场景。如果在onChange中判断会先显示最后一位输入的字符,然后判断不合法后删除,时机有些滞后,这里需要在字符显示之前拦截掉,所以考虑用onWillInsert。2、在onWillInsert中对输入之前对内容进行判断,当输入框内容大于等于999且将要插入的索引大于等于3,或者输入框内容长度等于3且内容不包含'.'将要输入的内容也非'.',则返回false,不让输入。

TextInput({ text: this.textTwo, placeholder: 'input your word...', controller: this.controllerTwo })
  .type(InputType.NUMBER_DECIMAL)// 带小数点的数字输入模式。
  .onChange((value: string) => {
    if (value === '.') {
      this.textTwo = '0.';
    } else {
      this.textTwo = value;
    }
  })
  .onWillInsert((info: InsertValue) => {
    if ((Number(this.textTwo) >= 999 && info.insertOffset >= 3) ||
      this.textTwo.length === 3 && info.insertValue !== '.' && this.textTwo.includes('.')===false) {
      return false
    }
    return true
  })

场景三:限制两位小数,不能以小数点开头的场景

效果图:

方案:

1、限制输入内容只能是两位小数,如在onChange中用正则判断输入内容是否符合要求,会导致光标有闪烁现象,根因同场景2因为onChange中判断时机滞后,这种场景需要在输入之前进行拦截2、在onChange中补齐0,如果直接输入.会在.之前补0.3、在onWillInsert中判断如果已输入的字符包含.且将要输入的字符是. 或如已输入的内容满足两位小数条件,返回false不让输入,否则返回true

TextInput({ text: this.textThree, placeholder: 'input your word...', controller: this.controllerThree })
  .type(InputType.NUMBER_DECIMAL)
  .onChange((value: string) => {
    // 当直接输入‘.’的时候自动补齐‘0.’,否则正常显示输入的字符
    if (value === '.') {
      this.textThree = '0.';
    } else {
      this.textThree = value;
    }
  })
    // 在返回true时,表示正常插入,返回false时,表示不插入
  .onWillInsert((info: InsertValue) => {
    // ^ 和 $ 分别指字符串的开始与结束,以数字开始,以小数点+两位数字结束
    let reg = /^\d+\.\d{2}$/
    // 如果输入的文本包含'.',且将要输入的字符是'.' 或者输入框的内容已经满足两位小数,返回false不让输入
    if (this.textThree.includes('.') && info.insertValue === '.' || reg.test(this.textThree)) {
      return false
    }
    return true
  })

场景四:TextInput如何在输入、粘贴、剪切动作之后,内容显示之前控制显示内容

效果图:

方案:

1、封装一个格式化字符串的函数,限制输入字符串数量,且每隔固定字符空一格。

formatStringFour(value: string) {
  let result = '';
  let num = 0; // 计算每隔固定字符空一格
  let message = ''; // 每固定字符为一段
  for(let i=0;i<value.length;i++){
    if(value[i] != ' '){
      message += value[i]
      num += 1
    }
    if(num == CHUNKSIZE){
      result += message + ' '
      num = 0
      message = ''
    }
  }
  result += message
  result = result.slice(0, LIMIT);
  return result;
}

2、通过记录下次光标设置的位置,设置光标的位置。

setCaret(controller:TextInputController) {
  if (this.nextCaret != -1) {
    console.log("to keep caret position right, change caret to", this.nextCaret)
    controller.caretPosition(this.nextCaret)
    this.nextCaret = -1
  }
}

3、在onWillInsert中对输入框字符数量做限制,当大于输入限制的字符数或者输入内容为空时不让输入,通过onTextSelectionChange中记录光标的位置,然后在onPaste回调处理光标前后以及粘贴的文字,并通过格式化字符串函数过滤输入的内容。

4、封装一个记录光标位置的函数,传入onChange改变前去空格的字符以及改变后去空格的字符,通过判断改变前长度小于改变后长度为插入场景,反之为删除的场景。在onChange中判断如果数字已经格式化完成了,在这个时候通过setCaret改变光标位置不会被重置掉,否则用calcCaretPosition记录下次光标位置,等格式化完成后设置。

calcCaretPosition(text: string, nextText: string) {
  let befNumberNoSpace: string = this.removeSpace(text)
  this.actualCh = 0
  if (befNumberNoSpace.length < this.numberNoSpace.length) { // 插入场景
    for (let i = 0; i < this.selectionStart; i++) {
      if (text[i] != ' ') {
        this.actualCh += 1
      }
    }
    this.actualCh += this.numberNoSpace.length - befNumberNoSpace.length
    for (let i = 0; i < nextText.length; i++) {
      if (nextText[i] != ' ') {
        this.actualCh -= 1
        if (this.actualCh <= 0) {
          this.nextCaret = i + 1
          break;
        }
      }
    }
  } else if (befNumberNoSpace.length > this.numberNoSpace.length) { // 删除场景
    if (this.selectionStart === text.length) {
      console.log("Caret at last, no need to change")
    } else if (this.selectionStart === this.selectionEnd) {
      // 按键盘上回退键一个一个删的情况
      for (let i = this.selectionStart; i < text.length; i++) {
        if (text[i] != ' ') {
          this.actualCh += 1
        }
      }
      for (let i = nextText.length - 1; i >= 0; i--) {
        if (nextText[i] != ' ') {
          this.actualCh -= 1
          if (this.actualCh <= 0) {
            this.nextCaret = i
            break;
          }
        }
      }
    } else {
      // 剪切/手柄选择 一次删多个字符
      this.nextCaret = this.selectionStart // 保持光标位置
      console.info(`chenbilian nextCaret:${this.nextCaret}`)
    }
  }
}
TextInput({ text: this.textFour, placeholder: 'input your word...', controller: this.controllerFour })
  .onWillInsert((info: InsertValue) => {
    console.log(`onWillInsert:${info.insertValue} ${info.insertOffset}`)
    // 当输入的字符串数量大于限制或者输入空格,返回false
    if (info.insertOffset >= LIMIT || info.insertValue === ' ' || this.textFour.length >= LIMIT) {
      return false
    }
    return true;
  })
  .onChange((value: string) => {
    this.numberNoSpace = this.removeSpace(value);
    let nextText: string = ""
    nextText = this.formatStringFour(value)
    if (this.textFour === nextText && nextText === value) {
      this.setCaret(this.controllerFour)
    } else {
      this.calcCaretPosition(this.textFour, nextText)
    }
    this.textFour = nextText
  })
  .onPaste((value: string, event: PasteEvent) => {
    // 获取粘贴板内容
    let clipboardText = value;
    // 记录当前光标前后的内容
    let textBeforeCursor = this.textFour.slice(0, this.selectionStart);
    let textAfterCursor = this.textFour.slice(this.selectionEnd);
    // 将粘贴的内容插入光标中间
    this.textFour = textBeforeCursor + clipboardText + textAfterCursor;
    // 通过条件过滤文本
    this.textFour = this.formatStringFour(this.textFour)
  })
  .onTextSelectionChange((selectionStart, selectionEnd) => {
    // 记录光标位置
    this.selectionStart = selectionStart
    this.selectionEnd = selectionEnd
  })

场景五:电话号码格式化时,删除号码中间的空格为删除前一位数字

方案:

1、相对于官方文档示例6,主要是在onChange中对删除的场景做判断,如果上次输入框字符长度大于当前的字符长度判断为删除,反之为插入。

2、通过判断删除的字符串索引8-9或者3-4的地方是否为空字符,如果不是空字符可以判断是删除了空格,再通过removeCharAt函数对输入框字符串做截取,将空格前一位数字删掉。

3、在onChange中判断如果数字已经格式化完成了,在这个时候通过setCaret改变光标位置不会被重置掉,否则用calcCaretPosition记录下次光标位置,等格式化完成后设置。

//删除指定位置的字符
removeCharAt(str:string, index:number) {
  if (index >= 0 && index < str.length) {
    return str.slice(0, index) + str.slice(index + 1);
  }
  return str; // 如果索引超出范围,返回原字符串
}
TextInput({...})
  .onChange((value:
this.teleNumberNoSpace = this.removeSpace(value);
console.log(':::number',value.substring(8, 9))
//判断 删除场景 去空字符串小于等于11 变动的字符串小于13 判断是否有特殊字符
if (this.textFive.length > value.length && this.teleNumberNoSpace.length <= 11
  && value.length<NUM_TEXT_MAXSIZE_LENGTH && this.checkNeedNumberSpace(value)) {
  //判断是否删除了空白字符,用空格分隔字符串
  let parts = value.split(' ')
  console.info(`parts:${parts.length}`)
  if (parts.length - 1 != 2) {
    //删除第二个空白字符场景
    if (value.substring(8, 9) != ' ') {
      //012 4567 9012
      value = this.removeCharAt(value,7)
    }
    //删除第一个空白字符场景
    if (value.substring(3, 4) != ' ') {
      //012 4567 9012
      value = this.removeCharAt(value,2)
    }
  }
}
//更新去空后的电话号码
this.teleNumberNoSpace = this.removeSpace(value);
let nextText: string = ""
if (this.teleNumberNoSpace.length > NUM_TEXT_MAXSIZE_LENGTH - 2) {
  nextText = this.teleNumberNoSpace
} else if (this.checkNeedNumberSpace(value)) {
  if (this.teleNumberNoSpace.length <= 3) {
    nextText = this.teleNumberNoSpace
  } else {
    let split1: string = this.teleNumberNoSpace.substring(0, 3)
    let split2: string = this.teleNumberNoSpace.substring(3)
    nextText = split1 + ' ' + split2
    if (this.teleNumberNoSpace.length > 7) {
      split2 = this.teleNumberNoSpace.substring(3, 7)
      let split3: string = this.teleNumberNoSpace.substring(7)
      nextText = split1 + ' ' + split2 + ' ' + split3
    }
  }
} else {
  nextText = value
}
console.log("onChange Triggered:" + this.textFive + "|" + nextText + "|" + value)
if (this.textFive === nextText && nextText === value) {
  // 此时说明数字已经格式化完成了 在这个时候改变光标位置不会被重置掉
  console.info(`chenbilian nextCaret1:${this.nextCaret}`)
  this.setCaret(this.controllerFive)
} else {
  this.calcCaretPosition(this.textFive,nextText)
}
this.textFive = nextText
})

场景六:textInput防抖与节流

方案:

1、防抖:规定事件内只触发一次,在事件触发后等待一定的延迟时间,如果在这段延迟时间内事件再次被触发,则重新开始计时封装一个输入框的防抖函数,当频繁触发某个事件时会清空计时器重新计时,这样可以达到防抖的效果,我们可以将需要请求的数据放在debouncedChangeValue 回调中处理。

debouncedChangeValue = this.debounce(() => {
  // 处理文本变化的逻辑,如请求搜索数据   300ms 的防抖时间
  console.log('debouncedChangeValue:', this.textSix);
}, 300);
// 防抖函数
private debounce(func: () => void, delay: number) {
  let timer: number | null = null; // 定时器
  return () => {
    if (timer !== null) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      func();
    }, delay);
  };
}
TextInput({ text: this.textSix, placeholder: 'input your word...', controller: this.controllerSix })
  .onChange((value: string)=>{
    this.textSix = value
    // 在debouncedOnChange中处理发送请求等逻辑
    this.debouncedChangeValue()
  })

2、节流:当持续触发事件时,保证一定时间段内只调用一次事件处理函数,防抖节流函数可以根据具体业务需求使用。

throttleChangeValue = this.throttle(() => {
  // 处理文本变化的逻辑,如请求搜索数据   1000ms 的节流时间
  console.info(`ChangeValue:${this.changeValue}`)
}, 1000);
// 节流函数
private throttle(fn: () => void, delay:number) {
  let timer : number | null = null; // 定时器
  let lastTime = 0 // 上一次执行时间
  return () => {
    const nowTime =Number(new Date()) // 获取当前时间
    const remainingTime = delay-(nowTime-lastTime) // 剩余时间
    console.info(`chen---nowTime:${nowTime}---remainingTime:${remainingTime}`)
    if(remainingTime <= 0){ // 如果剩余时间小于等于0
      clearTimeout(timer) // 清除定时器
      lastTime = nowTime // 更新上次执行时间
      fn()
    }else if(!timer){ // 如果定时器为null
      timer = setTimeout(()=>{ // 创建新的定时器
        lastTime = Number(new Date()) // 更新上一次执行时间
        timer = null // 清空定时器变量
        fn()
      },delay)
    }
  }
}

FAQ

1、关于textInput禁用拍照输入功能

textinput无法禁止拍摄输入,这是键盘自带功能,但是可以自定义键盘取消拍摄输入:支持开发者定制系统菜单选项,通过editmenuoptions接口,可以把菜单中的拍摄输入屏蔽掉editMenuOptions

2、关于textInput禁止粘贴。

修改onPaste中的相关实现逻辑,调用event.preventDefault!()可实现禁止粘贴。


HarmonyOS码上奇行
11.3k 声望4.1k 粉丝

欢迎关注 HarmonyOS 开发者社区:[链接]