鸿蒙应用开发-实用技巧-自定义PIN码输入框+自定义数字键盘

902 阅读3分钟

鸿蒙NEXT配图.webp

前文

在开发鸿蒙系统应用时,自定义PIN码输入框和数字键盘的需求在许多场景中非常常见,尤其是在涉及到安全输入或数字验证的场景中,比如支付验证、账户登录等。本文将详细解读如何实现一个自定义的PIN码输入框和数字键盘,先上效果图:

安装

ohpm install @pharos/pin_code_text_input

使用

import { PinCodeTextInput } from '@pharos/pin_code_text_input';

PinCodeTextInput({
  widgetWidth: '80%', // 宽度
  boxCount: 6, // 输入框个数
  enableClose: true, // 启用关闭按钮
  onInputCompleted: async (inputArray: Array<string>) => {
    const inputValue = inputArray.join(';')
    console.info('inputValue <<< ', inputValue)
  }, // 输入完毕
  onInputIntercept: async () => {
    return false
  }, // 是否拦截输入
  isPassword: false, // 是否密码输入, 若 true 输入框内容为遮掩内容
  isRequestFocus: true, // 是否获取焦点, 若true则弹出键盘
  enableKeyboardOnFocus: true, // 当焦点时, 是否弹出键盘
  safeAreaPadding: {
    bottom: 20,
  }, // 键盘的安全区域
  keyboardHeight:  270, // 键盘高度
  keyboardSupportAvoidance: true, // 键盘遮挡时,输入框是否自动避让
  keyboardTitleLabel: 'DIGIT KEYBOARD', // 键盘标题
})

PIN码输入框的实现

PIN码输入框组件PinCodeTextInput负责显示用户输入的内容,并根据设定显示为普通数字或密文(如*)。该组件允许用户设置多个参数,以适应不同的应用场景,包括输入框的宽度、格子数量、是否显示密文、键盘高度等。

核心属性

  • @Prop widgetWidth: 定义输入框的宽度。
  • @Prop boxCount: 输入框格子数量,默认是6。
  • @Prop isPassword: 是否以密文形式显示PIN码。
  • @Prop enableKeyboardOnFocus: 是否在输入框获得焦点时自动唤起键盘。
  • @Prop keyboardHeight: 定义数字键盘的高度。
  • @State caretIndex: 当前光标的位置。
  • @State textValueArray: 保存用户的输入内容。

构建输入框格子

通过buildBox方法,我们为每一个输入格子构建UI,并根据是否是密文来显示内容。在显示光标时,使用了一个闪烁的动画效果,使输入体验更加真实。

@Builder
buildBox(index: number, first?: boolean) {
  Stack() {
    Column()
      .opacity(this.isCaretOpacity ? 1 : 0)
      .animation({
        curve: curves.springMotion(),
        duration: 300,
      })
      .visibility(this.caretIndex === index ? Visibility.Visible : Visibility.None)
      .width(1)
      .height(20)
      .backgroundColor('#60999999')
    if (this.isPassword) {
      // 密文显示为 '*'
      Flex({
        justifyContent: FlexAlign.Center,
        alignItems: ItemAlign.Center,
      }) {
        Column()
          .borderRadius(45 / 2)
          .width(45 / 2)
          .height(45 / 2)
          .backgroundColor('#333')
          .visibility(this.getValue(index) === '*' ? Visibility.Visible : Visibility.Hidden)
      }
    } else {
      // 显示实际输入的数字
      Text(this.getValue(index)).fontSize(24)
        .lineHeight('100%')
        .textAlign(TextAlign.Center)
    }
  }
  .height('100%')
  .width(`calc(100% / ${this.boxCount})`)
  .border({
    width: {
      top: 1,
      left: first ? 1 : 0,
      bottom: 1,
      right: 1
    },
    color: '#46333333'
  }).flexGrow(1)
}

光标闪烁效果

通过定时器控制光标的透明度,实现光标的闪烁效果。这个定时器每600毫秒切换一次光标的透明度,使用户在输入时有明确的视觉提示。

setCaretOpacity() {
  this.isCaretOpacity = !this.isCaretOpacity
  this.timeoutId = setTimeout(() => {
    this.setCaretOpacity()
  }, 600)
}

数字键盘的实现

自定义键盘的功能

数字键盘的构建通过ProxyPinCodeTextInput组件来完成。该组件允许自定义键盘的外观与行为,包括键盘的高度、是否显示关闭按钮等。KeyboardBuilder方法负责构建数字键盘,并处理用户点击数字键或删除键的事件。

@Builder
KeyboardBuilder() {
  Keyboard({
    titleLabel: this.keyboardTitleLabel,
    keyboardType: this.keyboardType,
    enableClose: this.enableClose,
    onTapClose: () => {
      this.controller.stopEditing()
    },
    onTapKey: async (type, value) => {
      const block = await this.onInputIntercept().catch(() => false)
      if (block === true) {
        if (this.textValueArray.length == this.boxCount) {
          this.textValueArray = []
        }
        return
      }
      if (type === KeyType.DEL && this.textValueArray.length > 0) {
        this.textValueArray.pop()
        this.textValueArray = [...this.textValueArray]
      } else if (type === KeyType.VALUE && value && this.textValueArray.length < this.boxCount) {
        this.textValueArray.push(value)
        if (this.textValueArray.length == this.boxCount) {
          await this.onInputCompleted([...this.textValueArray]).catch(() => {
          })
          this.textValueArray = []
        }
      }
    },
  })
  .height(this.keyboardHeight)
  .padding(this.safeAreaPadding || { bottom: 20 })
  .backgroundColor('#FFF0F0F0')
}

TextInput()
    .customKeyboard(this.KeyboardBuilder(), {
      supportAvoidance: this.keyboardSupportAvoidance
    })

键盘输入逻辑

用户点击数字键时,数字会被添加到textValueArray中。 当输入达到boxCount(默认6个字符)时,会触发onInputCompleted事件,表示输入已完成。 用户可以使用删除键删除最后一位输入。

其他特性

输入拦截

通过onInputIntercept方法,可以控制是否允许继续输入。在某些情况下,例如账户被锁定或者达到最大重试次数时,可以通过拦截机制阻止用户输入。

粘贴功能

我们为输入框添加了粘贴功能,允许用户直接将文本粘贴到输入框中。如果文本符合条件(如非密码模式下只允许数字),系统会自动将其拆分并填充到输入框中。

.onPaste(async res => {
  if (this.isPassword !== true) {
    this.textValueArray = (res || '').replace(/\D/g, '').split('')
    if (this.textValueArray.length == this.boxCount) {
      await this.onInputCompleted([...this.textValueArray]).catch(() => {
      })
      this.textValueArray = []
    }
  }
})

代码地址

atomgit.com/pharosbard/…