项目演示




前言
在移动应用开发中,登录页面是用户与应用交互的第一道门槛,其设计质量直接影响用户体验和产品转化率。鸿蒙 HarmonyOS 作为新一代智能终端操作系统,提供了强大的 ArkUI 框架和 ArkTS 语言,为开发者构建高质量的用户界面提供了坚实基础。
本文将从零开始,带领读者一步步构建一个完整的登录表单页面。我们将从最基础的 Column 垂直布局开始,逐步引入 Row 水平布局、TextInput 输入框、Button 按钮、Image 图片等核心组件,最终实现一个功能完善、视觉精美的登录页面。
本文适用于具备 ArkTS 基础语法知识的开发者,建议 API Level 24 及以上版本。
一、ArkUI 布局体系概述
在开始构建登录表单之前,我们需要先了解 ArkUI 的布局体系。ArkUI 提供了多种布局容器,用于组织和排列页面上的组件。其中,Column 和 Row 是最基础也是最常用的线性布局容器。
1.1 Column 垂直布局容器
Column 容器将其子组件按照垂直方向从上到下依次排列。它是构建页面主体结构的基础容器。
基本语法:
typescript
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 | 所有间距完全相等 |
示例代码:
typescript
@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 容器将其子组件按照水平方向从左到右依次排列。它常用于构建横向排列的组件组,如导航栏、按钮组等。
基本语法:
typescript
Row({ space?: number }) {
// 子组件
}
.width('100%')
核心属性:
space:设置子组件之间的水平间距width:设置容器宽度height:设置容器高度justifyContent:设置子组件在主轴(水平方向)上的对齐方式alignItems:设置子组件在交叉轴(垂直方向)上的对齐方式
示例代码:
typescript
@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 是用于接收用户文本输入的核心组件,支持多种输入类型和丰富的样式配置。
基本语法:
typescript
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 | 电话号码输入 |
示例代码:
typescript
@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 组件用于响应用户的点击操作,支持多种按钮类型和自定义样式。
基本语法:
typescript
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 | 是否可用 |
示例代码:
typescript
@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 组件用于渲染展示图片,支持加载本地资源和网络图片。
基本语法:
typescript
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 | 按比例缩小以适应容器 |
加载本地资源:
typescript
Image($r('app.media.startIcon'))
.width(80)
.height(80)
.objectFit(ImageFit.Contain)
示例代码:
typescript
@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 组件用于选择单个或多个选项,常用于"记住密码"、"同意协议"等场景。
基本语法:
typescript
Checkbox()
.select(isSelected)
.onChange((value: boolean) => {
// 状态变化回调
})
常用属性:
| 属性名 | 类型 | 说明 |
|---|---|---|
| select | boolean | 是否选中 |
| selectedColor | ResourceColor | 选中状态颜色 |
| width | Length | 宽度 |
| height | Length | 高度 |
示例代码:
typescript
@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 组件用于在页面中添加分割线,常用于分隔不同区域的内容。
基本语法:
typescript
Divider()
.width('100%')
.color('#EEEEEE')
常用属性:
| 属性名 | 类型 | 说明 |
|---|---|---|
| width | Length | 宽度 |
| color | ResourceColor | 颜色 |
| strokeWidth | Length | 线条粗细 |
示例代码:
typescript
@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 装饰的变量是响应式的
- 当变量值改变时,依赖该变量的组件会自动重新渲染
- 状态变量必须在组件内部声明
状态变量声明:
typescript
@State username: string = ''
@State password: string = ''
@State isLoading: boolean = false
4.2 状态与组件绑定
输入框绑定:
typescript
TextInput({ text: this.username })
.onChange((value: string) => {
this.username = value
})
按钮状态绑定:
typescript
Button(this.isLoading ? '登录中...' : '登录')
.enabled(!this.isLoading)
复选框绑定:
typescript
Checkbox()
.select(this.rememberPassword)
.onChange((value: boolean) => {
this.rememberPassword = value
})
4.3 状态更新与UI渲染
当状态变量更新时,ArkUI 框架会自动触发组件的重新渲染:
typescript
handleLogin() {
this.isLoading = true // UI更新:按钮显示"登录中...",禁用状态
setTimeout(() => {
this.isLoading = false // UI更新:按钮恢复"登录",启用状态
}, 2000)
}
五、@Builder 组件复用
@Builder 装饰器用于创建可复用的 UI 片段,避免重复代码,提高代码的可维护性。
5.1 Builder 定义
typescript
@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 调用
typescript
build() {
Column() {
this.buildLogoArea() // 调用Builder
}
}
5.3 Builder 参数传递
Builder 可以接收参数,实现更灵活的复用:
typescript
@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 完整代码实现
typescript
@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
typescript
Column({ space: 24 }) {
// 所有页面内容
}
.width('100%')
.height('100%')
.backgroundColor('#F5F7FA')
- 设置
space: 24:为所有直接子组件设置24vp的垂直间距 - 设置
width('100%')和height('100%'):撑满整个屏幕 - 设置
backgroundColor('#F5F7FA'):设置页面背景色
第二层:表单区域 Column
typescript
Column({ space: 16 }) {
// 输入框和按钮
}
.padding({ left: 32, right: 32 })
.width('100%')
- 设置
space: 16:表单内部组件间距为16vp - 设置
padding({ left: 32, right: 32 }):左右各留32vp边距,使表单不贴边
第三层:输入框 Row
typescript
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. 状态变量分类
将状态变量按照功能分类,提高代码可读性:
typescript
// 输入相关状态
@State username: string = ''
@State password: string = ''
// UI显示相关状态
@State passwordVisible: boolean = false
@State rememberPassword: boolean = false
// 业务逻辑相关状态
@State isLoading: boolean = false
2. 状态更新时机
在事件回调中更新状态,确保状态变化与用户操作同步:
typescript
.onChange((text: string) => {
this.username = text // 立即更新状态
})
3. 避免不必要的状态更新
只在必要时更新状态,避免性能浪费:
typescript
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 更加灵活:
typescript
@Builder
buildInputItem(placeholder: Resource, iconPath: Resource, inputType: InputType, value: string)
3. 样式内聚
将样式属性放在 Builder 内部,避免外部依赖:
typescript
@Builder
buildLogoArea(): void {
Column({ space: 16 }) {
// 组件内容
}
.margin({ top: 60, bottom: 20 }) // 样式在内部设置
}
7.4 交互体验优化
1. 密码可见性切换
通过状态变量控制输入类型,实现密码显示/隐藏切换:
typescript
.type(this.passwordVisible ? InputType.Normal : InputType.Password)
2. 按钮加载状态
登录过程中禁用按钮,防止重复提交:
typescript
.enabled(!this.isLoading)
3. 表单验证反馈
添加基本的表单验证,提升用户体验:
typescript
if (this.username.length === 0 || this.password.length === 0) {
return
}
八、资源文件配置
8.1 字符串资源配置
在 resources/base/element/string.json 中添加字符串资源:
json
{
"string": [
{
"name": "username_placeholder",
"value": "请输入用户名"
},
{
"name": "password_placeholder",
"value": "请输入密码"
}
]
}
使用资源:
typescript
TextInput({ placeholder: $r('app.string.username_placeholder') })
8.2 图片资源
将图标文件放入 resources/base/media/ 目录,命名为 startIcon.png(或其他格式)。
使用图片资源:
typescript
Image($r('app.media.startIcon'))
九、常见问题与解决方案
9.1 Builder 链式调用错误
问题: 在 @Builder 调用后链式调用样式属性导致编译错误
原因: @Builder 方法返回 void,不能链式调用
解决方案: 将样式属性移到 Builder 内部
typescript
// 错误写法
this.buildLogoArea()
.margin({ top: 60 })
// 正确写法
@Builder
buildLogoArea(): void {
Column() {
// 内容
}
.margin({ top: 60 }) // 样式在内部设置
}
9.2 输入框类型切换错误
问题: 在 TextInput 构造函数中直接设置 type 属性
原因: TextInput 的 type 属性需要通过链式调用设置
解决方案: 使用 .type() 方法设置输入类型
typescript
// 错误写法
TextInput({ type: InputType.Password })
// 正确写法
TextInput()
.type(InputType.Password)
9.3 状态变量未初始化
问题: 使用未初始化的状态变量导致运行时错误
原因: ArkTS 要求所有变量必须初始化
解决方案: 在声明时初始化所有状态变量
typescript
@State username: string = '' // 正确:已初始化
@State password: string = '' // 正确:已初始化
9.4 导入语句位置错误
问题: import 语句放在其他语句之后
原因: ArkTS 要求所有 import 语句必须放在文件开头
解决方案: 将 import 语句移到文件最前面
typescript
import { InputType, FlexAlign } from '@kit.ArkUI'; // 必须在最前面
@Entry
@Component
struct Index {
// ...
}
十、扩展与进阶
10.1 添加表单验证
可以扩展登录功能,添加更完善的表单验证:
typescript
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 添加动画效果
可以为按钮和输入框添加交互动画:
typescript
Button('登录')
.width('100%')
.height(56)
.backgroundColor('#007DFF')
.borderRadius(12)
.stateEffect(true) // 开启点击效果
10.3 接入真实登录接口
将模拟登录替换为真实的网络请求:
typescript
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 集成路由跳转
添加页面跳转功能:
typescript
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 开发之路上取得更大的进步!
参考资料: