登录表单布局:从 Column 到完整表单 —— 鸿蒙 HarmonyOS ArkTS 原生学习指南
项目演示




前言
在移动应用开发中,登录页面是用户与应用交互的第一道门槛,其设计质量直接影响用户体验和产品转化率。鸿蒙 HarmonyOS 作为新一代智能终端操作系统,提供了强大的 ArkUI 框架和 ArkTS 语言,为开发者构建高质量的用户界面提供了坚实基础。
本文将从零开始,带领读者一步步构建一个完整的登录表单页面。我们将从最基础的 Column 垂直布局开始,逐步引入 Row 水平布局、TextInput 输入框、Button 按钮、Image 图片等核心组件,最终实现一个功能完善、视觉精美的登录页面。
本文适用于具备 ArkTS 基础语法知识的开发者,建议 API Level 24 及以上版本。
一、ArkUI 布局体系概述
在开始构建登录表单之前,我们需要先了解 ArkUI 的布局体系。ArkUI 提供了多种布局容器,用于组织和排列页面上的组件。其中,Column 和 Row 是最基础也是最常用的线性布局容器。
1.1 Column 垂直布局容器
Column 容器将其子组件按照垂直方向从上到下依次排列。它是构建页面主体结构的基础容器。
基本语法:
Column({ space?: number }) {
// 子组件
}
.width('100%')
.height('100%')
核心属性:
space:设置子组件之间的垂直间距width:设置容器宽度,可以是具体数值或百分比height:设置容器高度justifyContent:设置子组件在主轴(垂直方向)上的对齐方式alignItems:设置子组件在交叉轴(水平方向)上的对齐方式
justifyContent 常用取值:
| 属性值 | 效果说明 |
|---|---|
| FlexAlign.Start | 顶部对齐(默认值) |
| FlexAlign.Center | 垂直居中对齐 |
| FlexAlign.End | 底部对齐 |
| FlexAlign.SpaceBetween | 两端对齐,子组件之间间距均分 |
| FlexAlign.SpaceAround | 子组件两侧间距相等 |
| FlexAlign.SpaceEvenly | 所有间距完全相等 |
示例代码:
@Entry
@Component
struct ColumnDemo {
build() {
Column({ space: 20 }) {
Text('顶部内容')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text('中间内容')
.fontSize(18)
Text('底部内容')
.fontSize(16)
.fontColor('#666666')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('#F5F7FA')
}
}
1.2 Row 水平布局容器
Row 容器将其子组件按照水平方向从左到右依次排列。它常用于构建横向排列的组件组,如导航栏、按钮组等。
基本语法:
Row({ space?: number }) {
// 子组件
}
.width('100%')
核心属性:
space:设置子组件之间的水平间距width:设置容器宽度height:设置容器高度justifyContent:设置子组件在主轴(水平方向)上的对齐方式alignItems:设置子组件在交叉轴(垂直方向)上的对齐方式
示例代码:
@Entry
@Component
struct RowDemo {
build() {
Row({ space: 16 }) {
Text('左侧文本')
.fontSize(16)
Button('操作按钮')
.width(80)
.height(40)
}
.width('100%')
.padding({ left: 20, right: 20 })
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
}
}
1.3 布局嵌套原则
在实际开发中,我们通常需要将 Column 和 Row 进行嵌套使用,以实现复杂的页面布局。布局嵌套应遵循以下原则:
- 外层使用 Column:作为页面的主容器,负责垂直方向的内容排列
- 内层使用 Row:在需要横向排列组件时使用
- 保持层级清晰:避免过深的嵌套,通常不超过三层
- 合理使用 spacing:通过容器的 space 属性统一设置子组件间距,减少重复代码
二、登录表单需求分析
在开始编码之前,我们需要先分析登录表单的需求。一个完整的登录页面通常包含以下元素:
2.1 页面结构分析
- 顶部区域:应用 Logo 和应用名称
- 表单区域:用户名输入框、密码输入框
- 辅助功能区域:记住密码复选框、忘记密码链接
- 操作按钮区域:登录按钮
- 注册引导区域:注册链接
- 第三方登录区域:微信、QQ、微博等第三方登录方式
2.2 交互需求分析
- 输入框交互:支持文本输入、密码显示/隐藏切换
- 表单验证:非空验证
- 按钮状态:登录中显示加载状态,禁用重复点击
- 记住密码:支持复选框选择
- 链接跳转:忘记密码、注册、第三方登录点击事件
2.3 视觉设计需求
- 配色方案:清爽简洁的蓝白配色
- 圆角设计:输入框和按钮使用圆角
- 间距规范:统一的组件间距
- 响应式布局:适配不同屏幕尺寸
三、核心组件详解
3.1 TextInput 文本输入框
TextInput 是用于接收用户文本输入的核心组件,支持多种输入类型和丰富的样式配置。
基本语法:
TextInput({ placeholder?: ResourceStr, text?: ResourceStr })
.type(InputType.Normal)
.onChange((value: string) => {
// 输入变化回调
})
构造参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| placeholder | ResourceStr | 否 | 占位提示文本 |
| text | ResourceStr | 否 | 当前输入文本 |
常用属性:
| 属性名 | 类型 | 说明 |
|---|---|---|
| type | InputType | 输入类型(Normal/Password/Number/Email等) |
| width | Length | 组件宽度 |
| height | Length | 组件高度 |
| backgroundColor | ResourceColor | 背景颜色 |
| fontSize | Length | 字体大小 |
| fontColor | ResourceColor | 字体颜色 |
| placeholderColor | ResourceColor | 占位符颜色 |
| borderRadius | Length | 圆角半径 |
| padding | Padding | 内边距 |
| enabled | boolean | 是否可编辑 |
输入类型(InputType):
| 值 | 说明 |
|---|---|
| InputType.Normal | 普通文本输入 |
| InputType.Password | 密码输入(内容隐藏) |
| InputType.Number | 数字键盘 |
| InputType.Email | 邮箱输入 |
| InputType.PhoneNumber | 电话号码输入 |
示例代码:
@Entry
@Component
struct TextInputDemo {
@State username: string = ''
@State password: string = ''
build() {
Column({ space: 16 }) {
// 用户名输入框
TextInput({ placeholder: '请输入用户名', text: this.username })
.width('80%')
.height(56)
.backgroundColor('#FFFFFF')
.fontSize(16)
.placeholderColor('#CCCCCC')
.padding({ left: 16, right: 16 })
.borderRadius(12)
.onChange((value: string) => {
this.username = value
})
// 密码输入框
TextInput({ placeholder: '请输入密码', text: this.password })
.width('80%')
.height(56)
.backgroundColor('#FFFFFF')
.fontSize(16)
.placeholderColor('#CCCCCC')
.padding({ left: 16, right: 16 })
.borderRadius(12)
.type(InputType.Password)
.onChange((value: string) => {
this.password = value
})
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F5F7FA')
}
}
3.2 Button 按钮组件
Button 组件用于响应用户的点击操作,支持多种按钮类型和自定义样式。
基本语法:
Button(label?: ResourceStr, options?: { type?: ButtonType, stateEffect?: boolean })
.onClick(() => {
// 点击事件处理
})
构造参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| label | ResourceStr | 否 | 按钮文字 |
| options.type | ButtonType | 否 | 按钮类型(Capsule/Circle/Normal) |
| options.stateEffect | boolean | 否 | 是否开启点击效果 |
按钮类型(ButtonType):
| 值 | 说明 |
|---|---|
| ButtonType.Capsule | 胶囊按钮(默认),圆角为高度的一半 |
| ButtonType.Circle | 圆形按钮 |
| ButtonType.Normal | 普通按钮,默认圆角为0 |
常用属性:
| 属性名 | 类型 | 说明 |
|---|---|---|
| width | Length | 按钮宽度 |
| height | Length | 按钮高度 |
| backgroundColor | ResourceColor | 背景颜色 |
| fontSize | Length | 字体大小 |
| fontColor | ResourceColor | 字体颜色 |
| fontWeight | FontWeight | 字体粗细 |
| borderRadius | Length | 圆角半径(仅Normal类型) |
| enabled | boolean | 是否可用 |
示例代码:
@Entry
@Component
struct ButtonDemo {
@State isLoading: boolean = false
handleClick() {
this.isLoading = true
setTimeout(() => {
this.isLoading = false
}, 2000)
}
build() {
Column({ space: 16 }) {
// 胶囊按钮(默认)
Button('登录')
.width('80%')
.height(56)
.backgroundColor('#007DFF')
.fontSize(18)
.fontColor('#FFFFFF')
.onClick(() => {
this.handleClick()
})
// 普通按钮(自定义圆角)
Button('注册', { type: ButtonType.Normal })
.width('80%')
.height(56)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.fontSize(18)
.fontColor('#007DFF')
.onClick(() => {
// 注册逻辑
})
// 加载状态按钮
Button(this.isLoading ? '登录中...' : '登录')
.width('80%')
.height(56)
.backgroundColor('#007DFF')
.fontSize(18)
.fontColor('#FFFFFF')
.enabled(!this.isLoading)
.onClick(() => {
this.handleClick()
})
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F5F7FA')
}
}
3.3 Image 图片组件
Image 组件用于渲染展示图片,支持加载本地资源和网络图片。
基本语法:
Image(src: ResourceStr | PixelMap | DrawableDescriptor)
.objectFit(ImageFit.Contain)
常用属性:
| 属性名 | 类型 | 说明 |
|---|---|---|
| src | ResourceStr | 图片资源路径 |
| width | Length | 图片宽度 |
| height | Length | 图片高度 |
| objectFit | ImageFit | 图片缩放模式 |
| backgroundColor | ResourceColor | 背景颜色 |
| borderRadius | Length | 圆角半径 |
| padding | Padding | 内边距 |
图片缩放模式(ImageFit):
| 值 | 说明 |
|---|---|
| ImageFit.Contain | 保持宽高比完整显示 |
| ImageFit.Cover | 保持宽高比填满容器(裁剪超出部分) |
| ImageFit.Fill | 拉伸填满容器(不保持宽高比) |
| ImageFit.ScaleDown | 按比例缩小以适应容器 |
加载本地资源:
Image($r('app.media.startIcon'))
.width(80)
.height(80)
.objectFit(ImageFit.Contain)
示例代码:
@Entry
@Component
struct ImageDemo {
build() {
Column({ space: 20 }) {
// 圆形图标
Image($r('app.media.startIcon'))
.width(80)
.height(80)
.objectFit(ImageFit.Contain)
.backgroundColor('#E8F4FD')
.borderRadius(40)
.padding(16)
// 圆角图标
Image($r('app.media.startIcon'))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.backgroundColor('#F5F7FA')
.borderRadius(12)
}
.width('100%')
.height('100%')
.padding(20)
.justifyContent(FlexAlign.Center)
}
}
3.4 Checkbox 复选框组件
Checkbox 组件用于选择单个或多个选项,常用于"记住密码"、"同意协议"等场景。
基本语法:
Checkbox()
.select(isSelected)
.onChange((value: boolean) => {
// 状态变化回调
})
常用属性:
| 属性名 | 类型 | 说明 |
|---|---|---|
| select | boolean | 是否选中 |
| selectedColor | ResourceColor | 选中状态颜色 |
| width | Length | 宽度 |
| height | Length | 高度 |
示例代码:
@Entry
@Component
struct CheckboxDemo {
@State isChecked: boolean = false
build() {
Row({ space: 8 }) {
Checkbox()
.select(this.isChecked)
.selectedColor('#007DFF')
.onChange((value: boolean) => {
this.isChecked = value
})
Text('记住密码')
.fontSize(14)
.fontColor('#666666')
}
.padding(20)
}
}
3.5 Divider 分割线组件
Divider 组件用于在页面中添加分割线,常用于分隔不同区域的内容。
基本语法:
Divider()
.width('100%')
.color('#EEEEEE')
常用属性:
| 属性名 | 类型 | 说明 |
|---|---|---|
| width | Length | 宽度 |
| color | ResourceColor | 颜色 |
| strokeWidth | Length | 线条粗细 |
示例代码:
@Entry
@Component
struct DividerDemo {
build() {
Column({ space: 16 }) {
Text('上方内容')
.fontSize(16)
Divider()
.width('80%')
.color('#EEEEEE')
Text('下方内容')
.fontSize(16)
}
.padding(20)
}
}
四、@State 状态管理
在 ArkTS 中,@State 装饰器用于管理组件内部的响应式状态。当状态变量的值发生变化时,UI 会自动更新。
4.1 状态管理原理
响应式机制:
- 使用 @State 装饰的变量是响应式的
- 当变量值改变时,依赖该变量的组件会自动重新渲染
- 状态变量必须在组件内部声明
状态变量声明:
@State username: string = ''
@State password: string = ''
@State isLoading: boolean = false
4.2 状态与组件绑定
输入框绑定:
TextInput({ text: this.username })
.onChange((value: string) => {
this.username = value
})
按钮状态绑定:
Button(this.isLoading ? '登录中...' : '登录')
.enabled(!this.isLoading)
复选框绑定:
Checkbox()
.select(this.rememberPassword)
.onChange((value: boolean) => {
this.rememberPassword = value
})
4.3 状态更新与UI渲染
当状态变量更新时,ArkUI 框架会自动触发组件的重新渲染:
handleLogin() {
this.isLoading = true // UI更新:按钮显示"登录中...",禁用状态
setTimeout(() => {
this.isLoading = false // UI更新:按钮恢复"登录",启用状态
}, 2000)
}
五、@Builder 组件复用
@Builder 装饰器用于创建可复用的 UI 片段,避免重复代码,提高代码的可维护性。
5.1 Builder 定义
@Builder
buildLogoArea() {
Column({ space: 16 }) {
Image($r('app.media.startIcon'))
.width(80)
.height(80)
.objectFit(ImageFit.Contain)
Text('应用名称')
.fontSize(28)
.fontWeight(FontWeight.Bold)
}
}
5.2 Builder 调用
build() {
Column() {
this.buildLogoArea() // 调用Builder
}
}
5.3 Builder 参数传递
Builder 可以接收参数,实现更灵活的复用:
@Builder
buildInputItem(placeholder: Resource, inputType: InputType) {
TextInput({ placeholder: placeholder })
.type(inputType)
.width('100%')
.height(56)
}
// 调用
this.buildInputItem($r('app.string.username'), InputType.Normal)
5.4 Builder 使用注意事项
- 不能链式调用:@Builder 方法返回 void,不能在调用后链式调用样式属性
- 样式应在内部设置:所有样式属性应在 Builder 内部的组件上设置
- 状态访问:Builder 可以访问组件的 @State 变量
- 参数类型:参数应使用明确的类型,避免使用 any/unknown
六、登录表单完整实现
现在我们将所有知识点整合,实现一个完整的登录表单页面。
6.1 页面结构设计
┌─────────────────────────────────┐
│ │
│ Logo 区域 │
│ (图标 + 应用名 + 副标题) │
│ │
├─────────────────────────────────┤
│ │
│ 用户名输入框 │
│ (图标 + TextInput) │
│ │
│ 密码输入框 │
│ (图标 + TextInput + 切换) │
│ │
│ 辅助区域 │
│ (记住密码 + 忘记密码) │
│ │
│ 登录按钮 │
│ │
├─────────────────────────────────┤
│ │
│ 注册引导 │
│ ("还没有账号?立即注册") │
│ │
├─────────────────────────────────┤
│ │
│ 第三方登录 │
│ (分割线 + 图标 + 文字) │
│ │
└─────────────────────────────────┘
6.2 完整代码实现
@Entry
@Component
struct Index {
// ========== 状态变量定义 ==========
// 用户名输入状态
@State username: string = ''
// 密码输入状态
@State password: string = ''
// 密码可见性状态
@State passwordVisible: boolean = false
// 记住密码状态
@State rememberPassword: boolean = false
// 登录加载状态
@State isLoading: boolean = false
// ========== 事件处理方法 ==========
/**
* 登录按钮点击事件处理
* 包含表单验证和加载状态管理
*/
handleLogin(): void {
// 表单非空验证
if (this.username.length === 0 || this.password.length === 0) {
return
}
// 设置加载状态
this.isLoading = true
// 模拟登录请求延迟(实际项目中替换为真实网络请求)
setTimeout(() => {
this.isLoading = false
}, 2000)
}
// ========== UI Builder 方法 ==========
/**
* 构建Logo区域组件
* 包含应用图标、应用名称和副标题
*/
@Builder
buildLogoArea(): void {
Column({ space: 16 }) {
// 应用Logo图标
Image($r('app.media.startIcon'))
.width(80)
.height(80)
.objectFit(ImageFit.Contain)
.backgroundColor('#E8F4FD')
.borderRadius(20)
.padding(16)
// 应用名称
Text('鸿蒙登录示例')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#1A1A1A')
// 副标题
Text('欢迎来到HarmonyOS世界')
.fontSize(16)
.fontColor('#666666')
}
// 设置顶部和底部外边距
.margin({ top: 60, bottom: 20 })
}
/**
* 构建通用输入框组件
* @param placeholder 占位提示文本
* @param iconPath 左侧图标资源路径
* @param inputType 输入类型
* @param value 当前输入值
*/
@Builder
buildInputItem(placeholder: Resource, iconPath: Resource, inputType: InputType, value: string): void {
Row({ space: 12 }) {
// 左侧图标
Image(iconPath)
.width(24)
.height(24)
.objectFit(ImageFit.Contain)
// 文本输入框
TextInput({ placeholder: placeholder, text: value })
.width('100%')
.height(48)
.backgroundColor('#FFFFFF')
.fontSize(16)
.fontColor('#1A1A1A')
.placeholderColor('#CCCCCC')
.padding({ left: 0, right: 0 })
.type(inputType)
.onChange((text: string) => {
// 根据输入类型更新对应状态
if (inputType === InputType.Password) {
this.password = text
} else {
this.username = text
}
})
}
// 容器样式
.padding({ left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.height(56)
.width('100%')
}
/**
* 构建密码输入框组件(带显示/隐藏切换功能)
*/
@Builder
buildPasswordInput(): void {
Row({ space: 12 }) {
// 密码图标
Image($r('app.media.startIcon'))
.width(24)
.height(24)
.objectFit(ImageFit.Contain)
// 密码输入框
TextInput({ placeholder: $r('app.string.password_placeholder'), text: this.password })
.width('100%')
.height(48)
.backgroundColor('#FFFFFF')
.fontSize(16)
.fontColor('#1A1A1A')
.placeholderColor('#CCCCCC')
.padding({ left: 0, right: 0 })
.type(this.passwordVisible ? InputType.Normal : InputType.Password)
.onChange((text: string) => {
this.password = text
})
// 密码显示/隐藏切换按钮
Image($r('app.media.startIcon'))
.width(24)
.height(24)
.objectFit(ImageFit.Contain)
.onClick(() => {
this.passwordVisible = !this.passwordVisible
})
}
// 容器样式
.padding({ left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.height(56)
.width('100%')
}
/**
* 构建登录按钮组件
*/
@Builder
buildLoginButton(): void {
Button(this.isLoading ? '登录中...' : '登 录')
.width('100%')
.height(56)
.backgroundColor('#007DFF')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
.borderRadius(12)
.enabled(!this.isLoading)
.margin({ top: 8 })
.onClick(() => {
this.handleLogin()
})
}
/**
* 构建辅助功能区域
* 包含记住密码复选框和忘记密码链接
*/
@Builder
buildAuxiliaryArea(): void {
Row() {
// 记住密码区域
Row({ space: 8 }) {
Checkbox()
.select(this.rememberPassword)
.selectedColor('#007DFF')
.onChange((value: boolean) => {
this.rememberPassword = value
})
Text('记住密码')
.fontSize(14)
.fontColor('#666666')
}
// 忘记密码链接
Text('忘记密码?')
.fontSize(14)
.fontColor('#007DFF')
.onClick(() => {
// 跳转忘记密码页面逻辑
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.margin({ top: 8 })
}
/**
* 构建注册引导区域
*/
@Builder
buildRegisterArea(): void {
Row({ space: 8 }) {
Text('还没有账号?')
.fontSize(14)
.fontColor('#666666')
Text('立即注册')
.fontSize(14)
.fontColor('#007DFF')
.fontWeight(FontWeight.Medium)
.onClick(() => {
// 跳转注册页面逻辑
})
}
}
/**
* 构建第三方登录区域
* 包含分割线和第三方登录图标
*/
@Builder
buildThirdPartyLogin(): void {
Column({ space: 16 }) {
// 分割线区域
Row() {
Divider()
.width('35%')
.color('#EEEEEE')
Text('其他登录方式')
.fontSize(12)
.fontColor('#999999')
.width('30%')
.textAlign(TextAlign.Center)
Divider()
.width('35%')
.color('#EEEEEE')
}
// 第三方登录图标
Row({ space: 48 }) {
// 微信登录
Column({ space: 8 }) {
Image($r('app.media.startIcon'))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.backgroundColor('#E8F4FD')
.borderRadius(24)
.padding(8)
Text('微信')
.fontSize(12)
.fontColor('#666666')
}
// QQ登录
Column({ space: 8 }) {
Image($r('app.media.startIcon'))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.backgroundColor('#FFEBEB')
.borderRadius(24)
.padding(8)
Text('QQ')
.fontSize(12)
.fontColor('#666666')
}
// 微博登录
Column({ space: 8 }) {
Image($r('app.media.startIcon'))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.backgroundColor('#E8FDF0')
.borderRadius(24)
.padding(8)
Text('微博')
.fontSize(12)
.fontColor('#666666')
}
}
}
.width('100%')
.justifyContent(FlexAlign.End)
.margin({ bottom: 40 })
}
// ========== 主构建方法 ==========
/**
* 页面主构建方法
* 使用Column作为根容器,垂直排列所有表单元素
*/
build() {
// 外层Column:作为页面根容器
Column({ space: 24 }) {
// ========== 顶部Logo区域 ==========
this.buildLogoArea()
// ========== 表单内容区域 ==========
// 内层Column:包裹表单输入框,设置左右边距
Column({ space: 16 }) {
// 用户名输入框
this.buildInputItem(
$r('app.string.username_placeholder'),
$r('app.media.startIcon'),
InputType.Normal,
this.username
)
// 密码输入框(带显示/隐藏功能)
this.buildPasswordInput()
// 辅助功能区域(记住密码、忘记密码)
this.buildAuxiliaryArea()
// 登录按钮
this.buildLoginButton()
}
.padding({ left: 32, right: 32 })
.width('100%')
// ========== 注册引导区域 ==========
this.buildRegisterArea()
// ========== 第三方登录区域 ==========
this.buildThirdPartyLogin()
}
// 页面样式设置
.backgroundColor('#F5F7FA')
.height('100%')
.width('100%')
}
}
七、代码详解与最佳实践
7.1 布局层次分析
第一层:外层 Column
Column({ space: 24 }) {
// 所有页面内容
}
.width('100%')
.height('100%')
.backgroundColor('#F5F7FA')
- 设置
space: 24:为所有直接子组件设置24vp的垂直间距 - 设置
width('100%')和height('100%'):撑满整个屏幕 - 设置
backgroundColor('#F5F7FA'):设置页面背景色
第二层:表单区域 Column
Column({ space: 16 }) {
// 输入框和按钮
}
.padding({ left: 32, right: 32 })
.width('100%')
- 设置
space: 16:表单内部组件间距为16vp - 设置
padding({ left: 32, right: 32 }):左右各留32vp边距,使表单不贴边
第三层:输入框 Row
Row({ space: 12 }) {
Image(iconPath) // 图标
TextInput(...) // 输入框
}
.padding({ left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.height(56)
.width('100%')
- 设置
space: 12:图标和输入框之间间距为12vp - 设置白色背景和圆角:形成卡片式输入框效果
7.2 状态管理最佳实践
1. 状态变量分类
将状态变量按照功能分类,提高代码可读性:
// 输入相关状态
@State username: string = ''
@State password: string = ''
// UI显示相关状态
@State passwordVisible: boolean = false
@State rememberPassword: boolean = false
// 业务逻辑相关状态
@State isLoading: boolean = false
2. 状态更新时机
在事件回调中更新状态,确保状态变化与用户操作同步:
.onChange((text: string) => {
this.username = text // 立即更新状态
})
3. 避免不必要的状态更新
只在必要时更新状态,避免性能浪费:
handleLogin() {
if (this.username.length === 0 || this.password.length === 0) {
return // 验证失败,不更新状态
}
this.isLoading = true
}
7.3 Builder 组件复用技巧
1. 提取通用组件
将重复出现的 UI 模式提取为 Builder:
buildInputItem:通用输入框buildLoginButton:登录按钮buildAuxiliaryArea:辅助功能区域
2. 参数化 Builder
通过参数使 Builder 更加灵活:
@Builder
buildInputItem(placeholder: Resource, iconPath: Resource, inputType: InputType, value: string)
3. 样式内聚
将样式属性放在 Builder 内部,避免外部依赖:
@Builder
buildLogoArea(): void {
Column({ space: 16 }) {
// 组件内容
}
.margin({ top: 60, bottom: 20 }) // 样式在内部设置
}
7.4 交互体验优化
1. 密码可见性切换
通过状态变量控制输入类型,实现密码显示/隐藏切换:
.type(this.passwordVisible ? InputType.Normal : InputType.Password)
2. 按钮加载状态
登录过程中禁用按钮,防止重复提交:
.enabled(!this.isLoading)
3. 表单验证反馈
添加基本的表单验证,提升用户体验:
if (this.username.length === 0 || this.password.length === 0) {
return
}
八、资源文件配置
8.1 字符串资源配置
在 resources/base/element/string.json 中添加字符串资源:
{
"string": [
{
"name": "username_placeholder",
"value": "请输入用户名"
},
{
"name": "password_placeholder",
"value": "请输入密码"
}
]
}
使用资源:
TextInput({ placeholder: $r('app.string.username_placeholder') })
8.2 图片资源
将图标文件放入 resources/base/media/ 目录,命名为 startIcon.png(或其他格式)。
使用图片资源:
Image($r('app.media.startIcon'))
九、常见问题与解决方案
9.1 Builder 链式调用错误
问题: 在 @Builder 调用后链式调用样式属性导致编译错误
原因: @Builder 方法返回 void,不能链式调用
解决方案: 将样式属性移到 Builder 内部
// 错误写法
this.buildLogoArea()
.margin({ top: 60 })
// 正确写法
@Builder
buildLogoArea(): void {
Column() {
// 内容
}
.margin({ top: 60 }) // 样式在内部设置
}
9.2 输入框类型切换错误
问题: 在 TextInput 构造函数中直接设置 type 属性
原因: TextInput 的 type 属性需要通过链式调用设置
解决方案: 使用 .type() 方法设置输入类型
// 错误写法
TextInput({ type: InputType.Password })
// 正确写法
TextInput()
.type(InputType.Password)
9.3 状态变量未初始化
问题: 使用未初始化的状态变量导致运行时错误
原因: ArkTS 要求所有变量必须初始化
解决方案: 在声明时初始化所有状态变量
@State username: string = '' // 正确:已初始化
@State password: string = '' // 正确:已初始化
9.4 导入语句位置错误
问题: import 语句放在其他语句之后
原因: ArkTS 要求所有 import 语句必须放在文件开头
解决方案: 将 import 语句移到文件最前面
import { InputType, FlexAlign } from '@kit.ArkUI'; // 必须在最前面
@Entry
@Component
struct Index {
// ...
}
十、扩展与进阶
10.1 添加表单验证
可以扩展登录功能,添加更完善的表单验证:
handleLogin(): void {
// 用户名验证
if (this.username.length < 3) {
// 提示用户名太短
return
}
// 密码验证
if (this.password.length < 6) {
// 提示密码太短
return
}
// 正则验证(邮箱格式等)
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(this.username)) {
// 提示邮箱格式错误
return
}
// 执行登录逻辑
this.isLoading = true
}
10.2 添加动画效果
可以为按钮和输入框添加交互动画:
Button('登录')
.width('100%')
.height(56)
.backgroundColor('#007DFF')
.borderRadius(12)
.stateEffect(true) // 开启点击效果
10.3 接入真实登录接口
将模拟登录替换为真实的网络请求:
import { http } from '@kit.NetworkKit';
handleLogin(): void {
this.isLoading = true
http.request({
url: 'https://api.example.com/login',
method: http.RequestMethod.POST,
extraData: {
username: this.username,
password: this.password
}
})
.then((response) => {
// 登录成功处理
this.isLoading = false
})
.catch((error) => {
// 登录失败处理
this.isLoading = false
})
}
10.4 集成路由跳转
添加页面跳转功能:
import { router } from '@kit.CoreKit';
// 跳转注册页面
Text('立即注册')
.onClick(() => {
router.pushUrl({ url: 'pages/Register' })
})
// 跳转忘记密码页面
Text('忘记密码?')
.onClick(() => {
router.pushUrl({ url: 'pages/ForgotPassword' })
})
十一、总结
本文详细介绍了如何使用鸿蒙 HarmonyOS ArkTS 构建一个完整的登录表单页面。通过学习本文,您应该掌握了以下核心技能:
- Column/Row 布局容器的使用:理解垂直和水平布局的基本原理
- 核心组件的使用:TextInput、Button、Image、Checkbox、Divider 等组件
- @State 状态管理:掌握响应式状态的声明和使用
- @Builder 组件复用:学会提取可复用的 UI 片段
- 登录表单的完整实现:从需求分析到代码实现的完整流程
登录页面是移动应用中最基础也是最重要的页面之一。通过本文的学习,您可以举一反三,构建出更多高质量的表单页面,如注册页面、设置页面、个人信息页面等。
在实际开发中,建议结合鸿蒙官方文档和设计规范,不断优化用户体验和代码质量。祝您在 HarmonyOS 开发之路上取得更大的进步!
参考资料:
- 鸿蒙官方文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides
- ArkUI 组件参考:https://developer.huawei.com/consumer/cn/doc/harmonyos-references
- ArkTS 语言指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-basic-concepts-V5
更多推荐


所有评论(0)