鸿蒙新特性——TextInput 文本输入组件详解
一、引言
在移动端应用中,文本输入是最基础也是最频繁的用户交互方式。登录时需要输入用户名和密码,注册时需要填写邮箱和手机号,搜索时需要输入关键词,评论时需要输入内容——几乎每个页面的核心交互都离不开文本输入框。
移动端的文本输入远不止"敲几个字"那么简单。不同类型的输入内容需要不同的键盘布局:密码需要遮蔽显示并提供显隐切换,手机号需要数字键盘并限制位数,邮箱需要@符号快捷输入。在桌面端,键盘是外设,输入框只是一个接受字符串的矩形;但在移动端,输入框需要和软键盘紧密配合——键盘类型、回车键行为、聚焦时的滚动避让、密码的显示/隐藏切换——这些都是在桌面端不存在的交互维度。
传统实现一个完整的注册表单,需要为每个字段手动配置:输入框 + 占位符 + 键盘类型 + 输入校验 + 字符计数 + 密码显隐图标。即便用 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 的用法模式核心:
- 构造函数设置
placeholder和text(绑定到 @State) type()确定键盘类型- 样式链配置外观
onChange回调中更新 @State,实现双向绑定
2.5 密码字段的特殊处理
当 type 为 InputType.Password 或 InputType.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 注册流程:
- 5 个输入字段:用户名(Normal)、密码(Password)、确认密码(Password)、邮箱(Email)、手机号(PhoneNumber)
- 实时字符计数:用户名和手机号字段显示字符计数器
- 密码显隐切换:密码和确认密码字段支持一键切换明文/密文
- 表单校验:提交时验证所有字段(长度、格式、一致性)
- 错误提示:校验不通过时以红色卡片展示所有错误
- 成功反馈:校验通过后展示"注册信息已提交"卡片,列出提交的数据
- 重置功能:一键清空所有字段和错误状态
所有 5 个 TextInput 使用不同的 InputType,展示键盘类型的多样性。
3.2 表单字段与 InputType 映射
| 字段 | InputType | 特殊配置 | 校验规则 |
|---|---|---|---|
| 用户名 | Normal | maxLength=20, showCounter | 长度 >= 4 |
| 密码 | Password | showPasswordIcon | 长度 >= 6 |
| 确认密码 | Password | showPasswordIcon | 与密码一致 |
| 邮箱 | - | 邮箱格式 | |
| 手机号 | 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 圆角、浅灰背景。
密码字段的关键差异在于 type 和 showPasswordIcon:
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.Password或InputType.NEW_PASSWORD。不要用Normal+ 自定义遮蔽逻辑——Password 类型内置了密码保险箱联动、防止截屏、剪贴板管理等安全机制。 - 手机号/验证码:使用
InputType.PhoneNumber或InputType.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()) { /* ... */ }
})
推荐使用提交时校验。原因有三:
- 实时校验会打断用户输入——还没输完就弹出红色提示,体验很差
- 某些校验规则在输入过程中无法判断——比如"确认密码一致"在用户还没输入完确认密码时必然不一致
- 减少不必要的 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 实现实时数据绑定。
核心要点回顾:
-
InputType 是 TextInput 的核心:不同 InputType 决定键盘布局和输入行为。密码类自动遮蔽,数字类自动切换数字键盘,邮箱类提供 @ 快捷键。正确选择 InputType 是良好用户体验的基础。
-
密码字段推荐用 Password 类型:不要用 Normal + 自定义遮蔽。Password 类型内置了安全机制(截屏防护、密码保险箱联动),且通过
showPasswordIcon(true)即可启用显隐切换。 -
maxLength + showCounter 是字符限制的标准方案:两者配合自动显示"已输入/最大"格式,无需手动计算和渲染计数器。
-
onChange 中更新状态,提交时校验:不要在 onChange 中做校验(打断输入),而是仅在 onChange 中更新 @State,在提交按钮的 onClick 中统一校验。
-
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 注册功能的起点模板。
更多推荐




所有评论(0)