前言:为什么鸿蒙要用声明式UI?
做过安卓开发的同学一定对XML布局又爱又恨。写界面要先在XML里定义控件,然后回到Java/Kotlin里findViewById,再设置各种属性和监听事件。一个简单的按钮点击,就要写好几处代码,逻辑和UI完全分离,改个样式要来回切换文件。
传统XML布局的痛点:
- 代码量大,重复劳动多
- UI和逻辑分离,维护困难
- 状态更新繁琐,容易出错
- 无法直接在代码中看到UI结构
而鸿蒙的ArkUI声明式UI彻底解决了这些问题。你只需要描述UI应该是什么样子,框架会自动帮你完成剩下的工作。当状态发生变化时,UI会自动更新,再也不用手动去findViewById和setText了。
这篇文章我会带你彻底理解声明式UI的核心思想,掌握最常用的基础组件和布局,最后用一个完整的登录页面实战,帮你完成从XML到声明式的思维转变。
一、ArkUI开发框架介绍
ArkUI是华为为HarmonyOS NEXT打造的统一UI开发框架,提供了两种开发范式:
- 基于JS的Web范式(兼容旧版鸿蒙,纯血鸿蒙已不再推荐)
- 基于ArkTS的声明式范式(纯血鸿蒙唯一推荐的开发方式)
声明式UI的核心思想:
你只需要描述"UI在什么状态下应该是什么样子",当状态发生变化时,框架会自动更新UI来匹配这个状态。
简单来说,就是数据驱动UI。数据变了,UI自动跟着变,你不需要关心中间的更新过程。
举个最直观的例子:
- 命令式(安卓):先创建一个TextView,然后找到它,再调用setText("新内容")
- 声明式(鸿蒙):Text(this.content),当this.content变化时,文本自动更新
二、组件的基本结构
在ArkUI中,一切都是组件。页面是组件,按钮是组件,甚至一个文本也是组件。一个完整的自定义组件结构如下:
typescript
// 1. @Component装饰器:标记这是一个自定义组件
@Component
struct MyComponent {
// 2. 状态变量:驱动UI变化的数据
@State count: number = 0;
// 3. build()方法:组件的渲染函数,必须返回一个UI组件
build() {
// 4. 根组件:每个build()方法只能有一个根组件
Column() {
// 5. 子组件:描述UI结构
Text(`点击了${this.count}次`)
.fontSize(20); // 6. 属性方法:链式调用设置组件样式
Button("点击我")
.onClick(() => {
// 7. 事件处理:响应用户交互
this.count++;
});
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
}
和安卓XML+Java对比:
| 安卓写法 | 鸿蒙声明式写法 |
|---|---|
| XML定义布局 | build()方法中直接写UI |
| findViewById | 不需要,直接引用变量 |
| setText() | 直接绑定变量,自动更新 |
| setOnClickListener | 链式调用.onClick() |
| 布局文件+Java文件 | 一个文件搞定所有 |
三、常用基础组件
基础组件是构建UI的最小单元,掌握这四个组件,就能完成80%的界面开发。
3.1 Text:文本组件
用于显示文字内容,是最常用的组件。
typescript
Text("这是一段文本")
.fontSize(20) // 字体大小
.fontColor(Color.Red) // 字体颜色
.fontWeight(FontWeight.Bold) // 字体粗细
.textAlign(TextAlign.Center) // 文本对齐方式
.maxLines(1) // 最大行数
.textOverflow({ overflow: TextOverflow.Ellipsis }) // 超出部分显示省略号
.margin(10); // 外边距
3.2 Button:按钮组件
用于响应用户点击事件。
typescript
// 普通按钮
Button("普通按钮")
.width(200)
.height(50)
.backgroundColor('#007aff')
.onClick(() => {
console.log("按钮被点击了");
});
// 带图标的按钮
Button($r("app.media.icon"), "带图标按钮")
.width(200)
.height(50);
// 圆形按钮
Button("圆形按钮")
.width(100)
.height(100)
.borderRadius(50);
3.3 Image:图片组件
用于显示本地图片和网络图片。
typescript
// 本地资源图片(推荐)
Image($r("app.media.logo"))
.width(100)
.height(100)
.objectFit(ImageFit.Contain) // 图片缩放模式
.borderRadius(10);
// 网络图片
Image("https://example.com/image.jpg")
.width(200)
.height(150)
.alt($r("app.media.placeholder")); // 加载失败时显示的占位图
3.4 TextInput:输入框组件
用于接收用户输入的文本。
typescript
// 用户名输入框
TextInput({ placeholder: "请输入用户名" })
.width('100%')
.height(50)
.margin({ bottom: 15 })
.onChange((value) => {
console.log("输入的内容:" + value);
});
// 密码输入框
TextInput({ placeholder: "请输入密码" })
.type(InputType.Password) // 密码类型,输入内容会被隐藏
.width('100%')
.height(50);
四、布局组件
布局组件用来安排子组件的位置和大小,ArkUI提供了三种最基础也最常用的布局组件。
4.1 Column:垂直布局
子组件从上到下垂直排列。
typescript
Column() {
Text("第一行")
Text("第二行")
Text("第三行")
}
.width('100%')
.height(200)
.backgroundColor('#f5f5f5')
.justifyContent(FlexAlign.Center) // 主轴(垂直方向)对齐方式
.alignItems(HorizontalAlign.Center) // 交叉轴(水平方向)对齐方式
.gap(10); // 子组件之间的间距
4.2 Row:水平布局
子组件从左到右水平排列。
typescript
Row() {
Button("按钮1")
Button("按钮2")
Button("按钮3")
}
.width('100%')
.height(100)
.backgroundColor('#f5f5f5')
.justifyContent(FlexAlign.SpaceAround) // 主轴(水平方向)对齐方式
.alignItems(VerticalAlign.Center) // 交叉轴(垂直方向)对齐方式
.gap(10);
4.3 Stack:层叠布局
子组件堆叠在一起,后面的组件会覆盖前面的组件。
typescript
Stack() {
// 底层:背景图
Image($r("app.media.background"))
.width('100%')
.height(200);
// 上层:文字
Text("层叠在图片上的文字")
.fontSize(24)
.fontColor(Color.White)
.position({ x: 20, y: 20 }); // 绝对定位
}
.width('100%')
.height(200);
布局组合技巧: 复杂的界面都是通过这三种布局的嵌套组合实现的。比如一个列表项可以用Row布局,左边放图片,右边放Column布局,里面放标题和描述。
五、实战:写一个完整的登录页面
现在我们把前面学的所有组件和布局组合起来,写一个标准的登录页面。这个页面包含:logo、标题、用户名输入框、密码输入框、登录按钮、忘记密码和注册链接。
typescript
@Entry
@Component
struct LoginPage {
// 用户名和密码状态
@State username: string = "";
@State password: string = "";
build() {
Column() {
// 1. Logo区域
Image($r("app.media.logo"))
.width(100)
.height(100)
.margin({ top: 80, bottom: 40 });
// 2. 标题
Text("欢迎登录")
.fontSize(28)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 60 });
// 3. 输入框区域
TextInput({ placeholder: "请输入手机号/用户名" })
.width('85%')
.height(50)
.margin({ bottom: 15 })
.padding({ left: 15 })
.backgroundColor('#f5f5f5')
.borderRadius(8)
.onChange((value) => {
this.username = value;
});
TextInput({ placeholder: "请输入密码" })
.type(InputType.Password)
.width('85%')
.height(50)
.margin({ bottom: 20 })
.padding({ left: 15 })
.backgroundColor('#f5f5f5')
.borderRadius(8)
.onChange((value) => {
this.password = value;
});
// 4. 登录按钮
Button("登录")
.width('85%')
.height(50)
.backgroundColor('#007aff')
.fontSize(18)
.borderRadius(8)
.onClick(() => {
this.handleLogin();
});
// 5. 底部链接
Row() {
Text("忘记密码")
.fontColor('#007aff')
.onClick(() => {
console.log("点击了忘记密码");
});
Text("注册账号")
.fontColor('#007aff')
.onClick(() => {
console.log("点击了注册账号");
});
}
.width('85%')
.justifyContent(FlexAlign.SpaceBetween)
.margin({ top: 20 });
}
.width('100%')
.height('100%')
.backgroundColor('#ffffff');
}
// 登录处理函数
handleLogin() {
if (this.username === "" || this.password === "") {
console.log("用户名或密码不能为空");
return;
}
console.log(`登录中... 用户名:${this.username},密码:${this.password}`);
// 这里可以添加实际的登录逻辑
}
}
代码说明:
- 整个页面使用Column作为根布局,所有元素垂直排列
- 使用@State装饰器定义username和password状态变量,输入框的内容会自动同步到这两个变量
- 输入框使用TextInput组件,设置了placeholder、背景色和圆角
- 登录按钮使用Button组件,点击时调用handleLogin方法
- 底部的两个链接使用Row布局,左右分布
你可以直接把这段代码复制到DevEco Studio中运行,就能看到一个完整的登录页面了。
总结
今天我们学习了ArkUI声明式UI的核心思想和基础用法。最重要的是完成思维转变:从"我要一步步构建UI"变成"我要描述UI在不同状态下的样子"。
核心要点总结:
- 声明式UI是数据驱动的,数据变了,UI自动更新
- 一切都是组件,每个组件由@Component、struct、build()方法组成
- 掌握Text、Button、Image、TextInput四个基础组件
- 掌握Column、Row、Stack三个基础布局,通过嵌套组合实现复杂界面
- 多写多练,在实战中体会声明式UI的优势
福利时间!
我已经为大家准备了这篇文章中登录页面的完整源码,包含了更完善的样式和交互效果,还有输入验证、加载状态等功能。
获取方式:
关注我的账号,私信回复"登录页面",即可免费获取完整源码!
下一篇文章我会带大家深入学习鸿蒙的状态管理,教你如何在多个组件之间共享数据和传递状态。如果这篇文章对你有帮助,别忘了点赞、收藏、转发给你的朋友!有任何问题,欢迎在评论区留言。