一、引言

在移动端应用中,文本输入是最基础也是最频繁的用户交互方式。登录时需要输入用户名和密码,注册时需要填写邮箱和手机号,搜索时需要输入关键词,评论时需要输入内容——几乎每个页面的核心交互都离不开文本输入框。

移动端的文本输入远不止"敲几个字"那么简单。不同类型的输入内容需要不同的键盘布局:密码需要遮蔽显示并提供显隐切换,手机号需要数字键盘并限制位数,邮箱需要@符号快捷输入。在桌面端,键盘是外设,输入框只是一个接受字符串的矩形;但在移动端,输入框需要和软键盘紧密配合——键盘类型、回车键行为、聚焦时的滚动避让、密码的显示/隐藏切换——这些都是在桌面端不存在的交互维度。

传统实现一个完整的注册表单,需要为每个字段手动配置:输入框 + 占位符 + 键盘类型 + 输入校验 + 字符计数 + 密码显隐图标。即便用 ArkUI 的声明式语法,如果不了解 TextInput 的全部 API,开发者可能会走弯路——例如自己画一个眼睛图标来实现密码切换,而不使用内置的 showPasswordIcon

HarmonyOS 提供了 TextInput 组件——一个功能完备的单行文本输入框。它内置了对十几种输入类型(文本、密码、新密码、邮箱、数字、小数、电话号码、URL、用户名、验证码等)的支持,集成了密码显隐切换、字符计数、占位符样式、回车键类型等常用功能。开发者只需要声明 type 和绑定 onChange 回调,TextInput 自动处理键盘切换、密码遮蔽、输入过滤等逻辑。

本文通过一个用户注册表单 Demo 深入讲解 TextInput 组件的核心用法:如何配置不同的输入类型?如何使用密码显隐切换?如何实现字符计数?如何进行表单校验?如何通过 onChange 实时监听输入内容?

阅读完本文,你将能够:

  • 使用 InputType 枚举配置正确的键盘类型(Normal/Password/Email/PhoneNumber 等)
  • 使用 showPasswordIcon 实现密码一键显隐切换
  • 使用 maxLength + showCounter 实现字符限制与实时计数
  • 使用 placeholder + placeholderColor + placeholderFont 定制占位符
  • 通过 onChange 回调实时获取输入内容并实现联动校验
  • 在表单页面中组合多个 TextInput 构建完整的输入流程

二、TextInput 组件 API 总览

2.1 构造函数

TextInput(value?: TextInputOptions)

TextInput 的构造函数接收一个可选配置对象,定义初始值和占位符:

declare interface TextInputOptions {
  placeholder?: ResourceStr;  // 占位符文本(输入框为空时显示)
  text?: ResourceStr;         // 输入框的当前值
  controller?: TextInputController;  // 控制器(用于程序化操作光标)
}

最简用法——仅需占位符:

TextInput({ placeholder: '请输入用户名' })

带初始值的用法:

TextInput({ placeholder: '请输入用户名', text: '默认名称' })

2.2 InputType —— 输入类型枚举

TextInput 的核心能力之一是通过 type() 方法设置输入类型,不同的类型会触发不同的系统键盘布局和输入行为:

枚举值 键盘行为 适用场景
InputType.Normal 标准文本键盘 用户名、昵称、通用文本
InputType.Password 密码键盘(遮蔽字符 + 眼睛图标) 登录密码
InputType.NEW_PASSWORD 新密码键盘(可自动生成强密码) 注册密码
InputType.NUMBER_PASSWORD 纯数字密码键盘 支付密码、PIN 码
InputType.Email 邮箱键盘(含 @ 快捷键) 邮箱地址
InputType.Number 纯数字键盘 整数输入
InputType.NUMBER_DECIMAL 数字 + 小数点键盘 金额、体重等小数
InputType.PhoneNumber 电话号码键盘 手机号、座机号
InputType.URL URL 输入键盘 网址输入
InputType.USER_NAME 用户名键盘(与密码保险箱联动) 登录用户名的自动填充
InputType.ONE_TIME_CODE 一次性验证码键盘 短信验证码

关键规律:

  • 文本类(Normal/Email/URL):全键盘布局
  • 数字类(Number/NUMBER_DECIMAL/PhoneNumber):数字键盘布局
  • 密码类(Password/NEW_PASSWORD/NUMBER_PASSWORD):遮蔽字符 + 眼睛图标
  • 辅助类(USER_NAME/ONE_TIME_CODE):与系统自动填充服务联动

2.3 核心方法一览

TextInput 的方法分为几类:输入控制、样式定制、回调事件。

输入控制方法:

方法 用途 示例
type(value: InputType) 设置输入类型 .type(InputType.Password)
maxLength(value: number) 最大字符数 .maxLength(11)
showCounter(value: boolean) 显示字符计数器 .showCounter(true)
showPasswordIcon(value: boolean) 显示密码显隐切换图标 .showPasswordIcon(true)
enterKeyType(value: EnterKeyType) 设置软键盘回车键样式 .enterKeyType(EnterKeyType.Done)

样式定制方法:

方法 用途
placeholderColor(color) 占位符文字颜色
placeholderFont(font) 占位符字体(大小、粗细等)
fontSize(size) 输入文本字体大小
fontColor(color) 输入文本颜色
fontWeight(weight) 输入文本粗细
caretColor(color) 光标颜色
backgroundColor(color) 输入框背景色
borderRadius(radius) 输入框圆角

回调事件方法:

方法 触发时机 回调参数
onChange(callback) 输入内容变化时 (value: string) => void
onSubmit(callback) 按下回车键时 (enterKey: EnterKeyType) => void
onEditChange(callback) 编辑状态变化时(获得/失去焦点) (isEditing: boolean) => void
onCopy(callback) 复制文本时 (value: string) => void
onCut(callback) 剪切文本时 (value: string) => void
onPaste(callback) 粘贴文本时 (value: string) => void

2.4 基本用法模式

@State username: string = '';

TextInput({ placeholder: '请输入用户名', text: this.username })
  .type(InputType.Normal)
  .maxLength(20)
  .showCounter(true)
  .fontSize(14)
  .placeholderColor('#BBBBCC')
  .backgroundColor('#F8F9FA')
  .borderRadius(8)
  .height(44)
  .onChange((value: string) => {
    this.username = value;
  })

TextInput 的用法模式核心:

  1. 构造函数设置 placeholdertext(绑定到 @State)
  2. type() 确定键盘类型
  3. 样式链配置外观
  4. onChange 回调中更新 @State,实现双向绑定

2.5 密码字段的特殊处理

typeInputType.PasswordInputType.NEW_PASSWORD 时,TextInput 的行为与普通输入框不同:

  • 输入的字符默认被圆点遮蔽(●)
  • 可以通过 showPasswordIcon(true) 启用内置的眼睛图标
  • 用户点击眼睛图标时,密码临时明文显示
  • API 20 起,NEW_PASSWORD 还可以配合系统的密码自动生成功能

注意:showPasswordIcon 仅在 Password/NEW_PASSWORD/NUMBER_PASSWORD 类型下生效。如果使用 Normal 类型并期望有眼睛图标,需要自己实现(不推荐——直接用密码类型即可)。

2.6 onChange 与 $$ 双向绑定

onChange 是 TextInput 最常用的回调,在用户每次输入时触发。标准模式是在回调中将新值赋给 @State 变量:

TextInput({ text: this.username })
  .onChange((value: string) => { this.username = value; })

ArKUI 也支持 $$ 语法实现自动双向绑定(API 10+),但显式 onChange 更灵活——你可以在回调中做额外的处理(如过滤特殊字符、实时校验等)。
在这里插入图片描述

三、Demo 设计:用户注册表单

3.1 功能概述

Demo 是一个用户注册表单页面,模拟真实 App 注册流程:

  1. 5 个输入字段:用户名(Normal)、密码(Password)、确认密码(Password)、邮箱(Email)、手机号(PhoneNumber)
  2. 实时字符计数:用户名和手机号字段显示字符计数器
  3. 密码显隐切换:密码和确认密码字段支持一键切换明文/密文
  4. 表单校验:提交时验证所有字段(长度、格式、一致性)
  5. 错误提示:校验不通过时以红色卡片展示所有错误
  6. 成功反馈:校验通过后展示"注册信息已提交"卡片,列出提交的数据
  7. 重置功能:一键清空所有字段和错误状态

所有 5 个 TextInput 使用不同的 InputType,展示键盘类型的多样性。

3.2 表单字段与 InputType 映射

字段 InputType 特殊配置 校验规则
用户名 Normal maxLength=20, showCounter 长度 >= 4
密码 Password showPasswordIcon 长度 >= 6
确认密码 Password showPasswordIcon 与密码一致
邮箱 Email - 邮箱格式
手机号 PhoneNumber maxLength=11, showCounter 长度 = 11

每个字段的 InputType 都不同,体现了 TextInput 对不同输入场景的适配。

3.3 字段定义与状态管理

@State username: string = '';
@State password: string = '';
@State confirmPassword: string = '';
@State email: string = '';
@State phone: string = '';
@State errors: string[] = [];
@State submitted: boolean = false;

七个 @State 变量分别管理五个输入值、一个错误列表和一个提交状态。当 onChange 触发时,对应的 @State 更新;当提交按钮点击时,validate() 方法填充错误列表或设置 submitted = true

3.4 字段布局

每个字段采用统一的垂直布局——标签行 + 输入框:

// Username field
Column() {
  Row() {
    Text('用户名')
      .fontSize(14)
      .fontColor('#1a1a2e')
      .fontWeight(FontWeight.Medium)
    Text(' *')
      .fontSize(14)
      .fontColor('#FF4D4F')
  }
  .margin({ bottom: 6 })

  TextInput({ placeholder: '请输入用户名(4-20位)', text: this.username })
    .type(InputType.Normal)
    .maxLength(20)
    .showCounter(true)
    .backgroundColor('#F8F9FA')
    .borderRadius(8)
    .height(44)
    .width('100%')
    .fontSize(14)
    .placeholderColor('#BBBBCC')
    .onChange((value: string) => { this.username = value; })
}
.width('100%')
.margin({ bottom: Spacing.LG })

标签用 Row 组合——字段名 + 红色星号(必填标识),输入框统一配置为 44 高度、8 圆角、浅灰背景。

密码字段的关键差异在于 typeshowPasswordIcon

TextInput({ placeholder: '请输入密码(6-20位)', text: this.password })
  .type(InputType.Password)
  .showPasswordIcon(true)
  // ... 样式配置
  .onChange((value: string) => { this.password = value; })

showPasswordIcon(true) 会在输入框右侧显示一个眼睛图标。用户点击图标时,密码从圆点变为明文;再次点击恢复遮蔽。整个交互由 TextInput 内置实现,无需额外的状态管理。

手机号字段使用 InputType.PhoneNumber,自动切换到数字电话键盘布局:

TextInput({ placeholder: '请输入11位手机号码', text: this.phone })
  .type(InputType.PhoneNumber)
  .maxLength(11)
  .showCounter(true)
  // ... 样式配置

InputType.PhoneNumber 不仅提供数字键盘,还对输入内容做了限制——只接受数字和少量特殊字符(+、-、空格等)。

3.5 表单校验逻辑

validate(): boolean {
  this.errors = [];

  if (this.username.trim().length < 4) {
    this.errors.push('用户名至少需要4个字符');
  }
  if (this.password.length < 6) {
    this.errors.push('密码至少需要6个字符');
  }
  if (this.password !== this.confirmPassword) {
    this.errors.push('两次输入的密码不一致');
  }
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(this.email.trim())) {
    this.errors.push('请输入有效的邮箱地址');
  }
  if (this.phone.trim().length !== 11) {
    this.errors.push('手机号必须为11位数字');
  }

  return this.errors.length === 0;
}

校验逻辑依次检查五项规则,每项不通过就向 errors 数组推入一条错误信息。所有检查完成后,errors 为空表示校验通过。

3.6 错误提示与成功反馈

校验不通过时,errors 数组非空,页面时显一个红色边框的提示卡片:

if (this.errors.length > 0) {
  Column() {
    ForEach(this.errors, (err: string, idx: number) => {
      Row() {
        Text('⚠️')
          .fontSize(12)
          .margin({ right: 6 })
        Text(err)
          .fontSize(12)
          .fontColor('#FF4D4F')
      }
      .width('100%')
      .padding({ top: 3, bottom: 3 })
    })
  }
  .width('100%')
  .padding(Spacing.LG)
  .backgroundColor('#FFF2F0')
  .borderRadius(BorderRadius.LG)
  .border({ width: 1, color: '#FFCCC7' })
}

所有错误在同一个卡片中列出,用户可以一次性看到哪些字段需要修正——而不是每次只弹出一个错误提示。

校验通过后,submitted 变为 true,显示提交成功卡片:

if (this.submitted) {
  Column() {
    Text('✅ 注册信息已提交')
      .fontSize(15)
      .fontColor('#52C41A')
      .fontWeight(FontWeight.Bold)
      .margin({ bottom: Spacing.MD })

    Column() {
      this.dataRow('用户名', this.username)
      Divider().strokeWidth(0.5).color('#E8E8ED')
      this.dataRow('邮箱', this.email)
      Divider().strokeWidth(0.5).color('#E8E8ED')
      this.dataRow('手机号', this.phone)
    }
  }
}

注意:成功卡片只展示非敏感信息(用户名、邮箱、手机号),密码不在其中——这是实际应用中需要注意的安全细节。

3.7 页面结构

┌──────────────────────────────────────────┐
│ 📝 用户注册(深色标题栏)                 │
├──────────────────────────────────────────┤
│ 📘 TextInput 组件说明卡片                │
├──────────────────────────────────────────┤
│ ┌ 填写注册信息 ──────────────────────┐  │
│ │ 用户名 *                            │  │
│ │ [请输入用户名(4-20位)]  0/20      │  │
│ │                                     │  │
│ │ 密码 *                              │  │
│ │ [请输入密码(6-20位)]       👁     │  │
│ │                                     │  │
│ │ 确认密码 *                          │  │
│ │ [请再次输入密码]             👁     │  │
│ │                                     │  │
│ │ 邮箱 *                              │  │
│ │ [请输入邮箱地址]                     │  │
│ │                                     │  │
│ │ 手机号 *                            │  │
│ │ [请输入11位手机号码]         0/11    │  │
│ └─────────────────────────────────────┘  │
├──────────────────────────────────────────┤
│ [重置]              [提交注册]           │
├──────────────────────────────────────────┤
│ (错误提示卡片 — 校验不通过时显示)      │
│ (成功卡片 — 校验通过后显示)            │
└──────────────────────────────────────────┘

在这里插入图片描述

四、TextInput 组件的最佳实践

4.1 选择合适的 InputType

InputType 不仅影响系统键盘布局,还影响输入行为和安全特性:

  • 密码字段:始终使用 InputType.PasswordInputType.NEW_PASSWORD。不要用 Normal + 自定义遮蔽逻辑——Password 类型内置了密码保险箱联动、防止截屏、剪贴板管理等安全机制。
  • 手机号/验证码:使用 InputType.PhoneNumberInputType.Number,用户看到的是数字键盘,减少输入错误。
  • 邮箱:使用 InputType.Email,键盘提供 @ 和 . 的快捷输入。
  • 金额/小数:使用 InputType.NUMBER_DECIMAL,键盘提供数字和小数点,且限制只能输入一个点号。

选错 InputType 的代价不是编译错误,而是用户体验下降——用户打开注册页,看到全键盘而非常数字键盘,错误率会明显上升。

4.2 onChange 中的实时校验 vs 提交时校验

TextInput 的 onChange 在每次输入时触发。有两种校验策略:

实时校验(在 onChange 中校验):

.onChange((value: string) => {
  this.username = value;
  if (value.length > 20) {
    // 实时提示
  }
})

提交时校验(在提交按钮点击时校验):

.onChange((value: string) => {
  this.username = value;  // 仅更新状态,不校验
})

Button('提交').onClick(() => {
  if (this.validate()) { /* ... */ }
})

推荐使用提交时校验。原因有三:

  1. 实时校验会打断用户输入——还没输完就弹出红色提示,体验很差
  2. 某些校验规则在输入过程中无法判断——比如"确认密码一致"在用户还没输入完确认密码时必然不一致
  3. 减少不必要的 UI 刷新——onChange 高频触发,在其中做校验逻辑会增加组件刷新频率

唯一的例外是字符计数(showCounter)——它是非侵入性的提示,不打断用户,适合实时显示。

4.3 maxLength + showCounter 的协同

maxLength 限制最大字符数,showCounter 显示当前已输入字符数。两者配合使用时,计数器会自动显示为"已输入/最大"格式(如 5/20):

TextInput({ placeholder: '请输入用户名', text: this.username })
  .type(InputType.Normal)
  .maxLength(20)
  .showCounter(true)

注意事项:

  • maxLength 限制的是字符数(不是字节数),中文和英文各算 1 个字符
  • 达到 maxLength 后,软键盘不接受新字符(但粘贴操作可能被截断)
  • showCounter 的显示位置在输入框底部右侧,占用少量垂直空间

4.4 在 Scroll 中嵌入 TextInput

移动端表单通常很长(多字段 + 按钮 + 提示信息),需要放在 Scroll 中滚动。但 TextInput 在 Scroll 中有个常见问题:聚焦时软键盘弹起可能遮挡输入框。

HarmonyOS 的 Scroll 组件默认会自动处理键盘避让——当输入框聚焦时,页面自动上移确保输入框可见。但如果表单很短(不到一屏),可能不需要 Scroll;如果表单很长,Scroll 配合自动避让是标准方案。

Scroll() {
  Column() {
    // 多个 TextInput 字段
  }
}
.layoutWeight(1)

Scroll 使用 layoutWeight(1) 占据标题栏下方的全部空间,确保内容超出屏幕时可以滚动。

4.5 showPasswordIcon 的局限

showPasswordIcon(true) 仅在 InputType.Password/NEW_PASSWORD/NUMBER_PASSWORD 下有效。如果希望在普通输入框中添加眼睛图标(例如搜索框中的清除按钮),需要在 TextInput 外部自定义图标——TextInput 本身不提供自定义尾部图标的 API。

另外,showPasswordIcon 使用的是系统内置的眼睛图标。如果设计稿要求自定义图标样式,需要设置 showPasswordIcon(false)(或不设置),然后在 TextInput 外部自己实现切换逻辑——不过这会失去 Password 类型的部分安全特性。

4.6 TextInput 与 TextArea 的选择

TextInput 是单行输入框,TextArea 是多行文本框。选择规则:

特性 TextInput TextArea
行数 1 行(固定) 多行(可滚动)
回车键 触发 onSubmit 换行
适用场景 用户名、密码、手机号、搜索 评论、简介、反馈、备注
高度 固定(~44vp) 可自定义

选择规则:单行、固定高度、回车提交 → TextInput。多行、内容可能较长、回车换行 → TextArea。

五、完整代码结构

TextInputPage (~300 行)
├── 状态变量
│   ├── @State username/password/confirmPassword/email/phone — 5 个输入值
│   ├── @State errors: string[] — 校验错误列表
│   └── @State submitted: boolean — 提交状态
├── 方法
│   ├── validate(): boolean — 表单校验逻辑
│   ├── submitForm() — 提交处理
│   └── resetForm() — 重置所有字段
├── 视图
│   ├── 标题栏 — 📝 用户注册
│   ├── 说明卡片 — TextInput 组件介绍
│   ├── 表单卡片(5 个字段,每个独立 Column 布局)
│   ├── 错误提示卡片(条件渲染)
│   ├── 按钮行(重置 + 提交)
│   └── 成功反馈卡片(条件渲染)
└── @Builder
    └── dataRow() — 数据展示行

六、总结

本文通过一个用户注册表单 Demo 深入讲解了 HarmonyOS 中的 TextInput 单行文本输入框组件。TextInput 将文本输入的核心交互——键盘类型切换、密码遮蔽、字符计数、占位符、输入校验——封装为一个统一的声明式组件,通过 type() 切换十几种输入模式,通过 showPasswordIcon 一键启用密码显隐,通过 onChange 实现实时数据绑定。

核心要点回顾:

  1. InputType 是 TextInput 的核心:不同 InputType 决定键盘布局和输入行为。密码类自动遮蔽,数字类自动切换数字键盘,邮箱类提供 @ 快捷键。正确选择 InputType 是良好用户体验的基础。

  2. 密码字段推荐用 Password 类型:不要用 Normal + 自定义遮蔽。Password 类型内置了安全机制(截屏防护、密码保险箱联动),且通过 showPasswordIcon(true) 即可启用显隐切换。

  3. maxLength + showCounter 是字符限制的标准方案:两者配合自动显示"已输入/最大"格式,无需手动计算和渲染计数器。

  4. onChange 中更新状态,提交时校验:不要在 onChange 中做校验(打断输入),而是仅在 onChange 中更新 @State,在提交按钮的 onClick 中统一校验。

  5. TextInput 是表单页面的基础组件:配合不同的 InputType、校验逻辑和提交反馈,TextInput 可以构建从简单搜索框到复杂注册表单的各种输入场景。

TextInput 的 API 方法多达 100+(从基础样式到高级功能如 customKeyboard、autofill、内容过滤),但 80% 的场景只需要 type + placeholder + onChange + 基本样式配置。本文覆盖的是每个使用 TextInput 的开发者都需要掌握的核心模式——掌握这些后,再根据具体需求查阅高级 API 即可。

七、扩展思考

TextInput 覆盖了基础的文本输入需求,但实际项目中的输入框场景更加丰富:

TextArea 多行输入:当用户需要输入长文本(评论、简介、反馈)时,单行的 TextInput 不够用。TextArea 提供了多行输入能力,但失去了 InputType 的类型支持——大部分高级输入类型(Password、Email 等)仅在单行场景有意义。

与 TextPicker/Select 的配合:表单页面中,不是所有字段都需要自由输入。手机验证码适合 TextInput + 倒计时按钮的组合;性别/地区等枚举选择适合 Select 或 TextPicker;出生日期适合 DatePicker。在一个完整的注册表单中,TextInput 和这些选择类组件通常混合使用。

内容过滤与输入限制maxLength 限制字符数,但如果需要限制输入字符类型(例如只能输入中文、禁止特殊字符),需要使用 inputFilter 方法配合正则表达式。更复杂的内容过滤(如敏感词过滤)需要在 onChange 中自行处理。

与后端校验的配合:前端校验(格式、长度、必填)是用户体验的第一层,但最终校验在后端(用户名是否已注册、验证码是否正确)。在 onSubmit 回调中触发后端校验,校验结果通过 @State 驱动 UI 更新——这与前端校验的渲染模式一致。

密码强度指示器:注册场景中,通常需要告知用户密码强度。可以在 onChange 中实时分析密码复杂度(是否含数字、大小写字母、特殊字符),并在输入框下方渲染强度指示条——这是 TextInput + 自定义 UI 的经典组合。

理解 TextInput 的定位——单行、类型驱动、轻量级的文本输入组件——是正确使用它的关键。它不是富文本编辑器(那是 RichEditor 的职责),不是多行文本域(那是 TextArea 的职责),而正是这种专注让它成为了移动端表单中最基础、最高频、最灵活的输入交互方案。

通过本文的 Demo——用户注册表单页面,你将 5 个不同 InputType 的 TextInput 组件组织在完整的注册流程中,体验了从输入、校验到提交反馈的标准模式。这个页面可以作为任何 App 注册功能的起点模板。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐