鸿蒙UI开发基础------核心组件、样式系统与资源管理🖌️
学习目标
- 掌握鸿蒙5大核心UI组件(Text/Button/Image/TextField/Toggle)的高级用法
- 学会用鸿蒙资源系统管理文本、颜色、尺寸,避免硬编码
- 理解样式复用机制(@Styles/@Extend),写出可维护的UI代码
- 开发完整的「个人信息编辑页」,整合所有UI基础知识点
- 解决UI开发入门90%的常见问题(如组件不居中、图片加载失败等)
开篇:第1篇课后练习答案解析
在上一篇的课后练习中,我给大家留了4个小任务,现在公布标准实现方案,你可以对比自己的代码查漏补缺:
1.1 练习1:修改文本颜色为蓝色,字体大小35px
ets
// 修改前:Text(this.message).fontSize(30).fontColor(Color.Red)
Text(this.message)
.fontSize(35) // 修改为35px
.fontColor(Color.Blue) // 修改为蓝色
.margin({ top: 50 })
.fontWeight(FontWeight.Bold)
1.2 练习2:新增「重置」按钮,点击后计数器清零
ets
// 在原来的Button下方新增
Button('重置计数')
.fontSize(24)
.width(280)
.height(50)
.margin({ top: 20 })
.backgroundColor(Color.Gray) // 加灰色背景区分
.onClick(() => {
this.counter = 0 // 重置为0
})
1.3 练习3:EntryAbility生命周期日志打印
typescript
// EntryAbility.ts 完整代码
import { Ability } from '@ohos.app.ability';
export default class EntryAbility extends Ability {
onCreate(want, launchParam) {
console.info("✅ EntryAbility 已创建");
}
onForeground(want, launchParam) {
console.info("✅ EntryAbility 进入前台");
}
onBackground() {
console.info("✅ EntryAbility 进入后台");
}
onDestroy() {
console.info("✅ EntryAbility 已销毁");
}
}
// 查看日志:DevEco Studio → Logcat → 筛选"EntryAbility"关键字
1.4 练习4:真机运行准备(提前预告)
真机运行需要申请应用签名 和设备调试授权,这个知识点会在「第5篇:鸿蒙应用打包与发布」中详细讲解,现在可以先跳过。
二、鸿蒙核心基础UI组件详解
鸿蒙的UI组件分为基础组件 、容器组件 和扩展组件 ,本章先聚焦最常用的5个基础组件,它们覆盖了90%的UI开发场景。
2.1 文本组件 Text------富文本与交互
Text是展示文本的核心组件,除了基础的字体样式,还支持富文本 和事件监听。
2.1.1 核心属性与方法
| 属性/方法 | 功能 | 示例 |
|---|---|---|
fontSize() |
设置字体大小 | fontSize(28) |
fontColor() |
设置字体颜色 | fontColor(Color.Orange) |
fontWeight() |
设置字体粗细 | fontWeight(FontWeight.Bold) |
lineHeight() |
设置行高 | lineHeight(40) |
onLongPress() |
长按事件 | onLongPress(() => console.log('长按')) |
maxLines() |
最大行数 | maxLines(2) |
textOverflow() |
溢出显示 | textOverflow({ overflow: TextOverflow.Ellipsis }) |
2.1.2 富文本实现(API 9+)
⌨️ 富文本示例代码:
ets
import { Color, FontWeight, TextOverflow } from '@ohos/arkui';
@Entry
@Component
struct TextDemo {
build() {
Row() {
Column() {
// 普通文本
Text('基础文本')
.fontSize(24)
.margin({ bottom: 20 })
// 富文本:不同片段不同样式
Text()
.appendSpan(TextSpan { text: '鸿蒙', color: Color.Red, fontWeight: FontWeight.Bold })
.appendSpan(TextSpan { text: 'UI开发', color: Color.Blue, fontSize: 28 })
.appendSpan(TextSpan { text: '基础组件', color: Color.Green, fontWeight: FontWeight.Light })
// 超长文本省略号
Text('这是一段超长的文本,用于测试文本溢出时的省略号显示效果...')
.fontSize(20)
.width(280)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ top: 20 })
}
.width('100%')
.alignItems(HorizontalAlign.Center)
}
.height('100%')
}
}
2.2 按钮组件 Button------样式与状态
Button是用户交互的核心组件,支持3种预设样式 和自定义样式 ,以及状态监听。
2.2.1 核心类型与属性
| 类型 | 功能 | 示例 |
|---|---|---|
ButtonType.Capsule |
胶囊按钮(默认) | type(ButtonType.Capsule) |
ButtonType.Circle |
圆形按钮 | type(ButtonType.Circle).width(50).height(50) |
ButtonType.Normal |
矩形按钮 | type(ButtonType.Normal).borderRadius(5) |
backgroundColor() |
背景颜色 | backgroundColor(Color.Pink) |
onTouch() |
触摸事件 | onTouch((event) => console.log(event.type)) |
enabled() |
禁用状态 | enabled(false) |
2.2.2 多种按钮样式示例
⌨️ 按钮样式代码:
ets
import { ButtonType, Color } from '@ohos/arkui';
@Entry
@Component
struct ButtonDemo {
build() {
Row() {
Column() {
// 1. 胶囊按钮(默认)
Button('胶囊按钮')
.fontSize(20)
.width(200)
.height(50)
.margin({ bottom: 20 })
// 2. 圆形按钮(需固定宽高)
Button('+')
.type(ButtonType.Circle)
.width(60)
.height(60)
.backgroundColor(Color.Green)
.fontSize(30)
.margin({ bottom: 20 })
// 3. 矩形按钮
Button('矩形按钮')
.type(ButtonType.Normal)
.width(150)
.height(50)
.borderRadius(8) // 圆角
.backgroundColor(Color.Orange)
.margin({ bottom: 20 })
// 4. 禁用状态按钮
Button('禁用按钮')
.fontSize(20)
.width(200)
.height(50)
.enabled(false) // 禁用
.backgroundColor(Color.Gray)
}
.width('100%')
.alignItems(HorizontalAlign.Center)
}
.height('100%')
}
}
2.3 图片组件 Image------本地/网络图片加载
Image用于显示图片,支持本地资源图片 、网络图片 和base64图片 ,还可以设置裁剪 和缩放。
2.3.1 核心属性
| 属性 | 功能 | 示例 |
|---|---|---|
source() |
图片来源 | 本地:source($r("app.media.logo"));网络:source({ uri: "https://xxx.png" }) |
objectFit() |
缩放模式 | objectFit(ImageFit.Cover)(填充容器,保持比例) |
objectRepeat() |
重复模式 | objectRepeat(ImageRepeat.XY)(横向纵向重复) |
width()/height() |
宽高 | width(150).height(150) |
⚠️ 避坑点 :加载网络图片需要在config.json中声明网络权限:
json
// config.json → module → reqPermissions
{
"name": "ohos.permission.INTERNET",
"reason": "需要访问网络加载图片",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
}
2.3.2 图片加载示例
⌨️ 图片加载代码:
ets
import { ImageFit } from '@ohos/arkui';
@Entry
@Component
struct ImageDemo {
build() {
Row() {
Column() {
// 1. 本地资源图片(需将logo.png放入resource/base/media目录)
Image($r("app.media.logo")) // 资源引用方式:$r(资源类型.资源目录.资源名)
.width(180)
.height(180)
.objectFit(ImageFit.Contain)
.margin({ bottom: 30 })
// 2. 网络图片(需加INTERNET权限)
Image({ uri: "https://developer.harmonyos.com/cn/images/logo.png" })
.width(200)
.height(200)
.objectFit(ImageFit.Cover)
}
.width('100%')
.alignItems(HorizontalAlign.Center)
}
.height('100%')
}
}
2.4 输入框组件 TextField------输入监听与验证
TextField用于接收用户输入,支持占位符 、输入类型 (文本/数字/密码)和实时监听。
2.4.1 核心属性与事件
| 属性/事件 | 功能 | 示例 |
|---|---|---|
placeholder() |
占位符 | placeholder('请输入姓名') |
type() |
输入类型 | type(InputType.Password)(密码) |
onChange() |
输入变化事件 | onChange((value) => console.log(value)) |
maxLength() |
最大长度 | maxLength(10) |
enterKeyType() |
回车键类型 | enterKeyType(EnterKeyType.Send)(发送) |
2.4.2 输入框示例
⌨️ 输入框代码:
ets
import { InputType, EnterKeyType } from '@ohos/arkui';
@Entry
@Component
struct TextFieldDemo {
@State username: string = ''
@State password: string = ''
build() {
Row() {
Column() {
// 用户名输入
TextField({ placeholder: '请输入用户名', text: this.username })
.fontSize(20)
.width(320)
.height(55)
.margin({ bottom: 20 })
.backgroundColor('#F5F5F5')
.borderRadius(8)
.padding({ left: 15 }) // 内边距,避免文字贴边
.onChange((value) => {
this.username = value
})
// 密码输入
TextField({ placeholder: '请输入密码', text: this.password })
.type(InputType.Password) // 密码类型,输入时显示***
.fontSize(20)
.width(320)
.height(55)
.margin({ bottom: 30 })
.backgroundColor('#F5F5F5')
.borderRadius(8)
.padding({ left: 15 })
.onChange((value) => {
this.password = value
})
// 提交按钮
Button('登录')
.fontSize(20)
.width(320)
.height(55)
.onClick(() => {
console.log('登录:用户名=' + this.username + ', 密码=' + this.password)
})
}
.width('100%')
.alignItems(HorizontalAlign.Center)
}
.height('100%')
}
}
2.5 开关组件 Toggle------多选与切换
Toggle用于开关状态或复选功能,支持开关样式 和复选框样式。
2.5.1 核心属性
| 属性 | 功能 | 示例 |
|---|---|---|
type() |
组件类型 | type(ToggleType.Switch)(开关);type(ToggleType.Checkbox)(复选框) |
isOn() |
初始状态 | isOn(true)(默认开启) |
selectedColor() |
选中颜色 | selectedColor(Color.Red) |
onChange() |
状态变化事件 | onChange((isOn) => console.log(isOn)) |
2.5.2 Toggle示例
⌨️ Toggle代码:
ets
import { ToggleType, Color } from '@ohos/arkui';
@Entry
@Component
struct ToggleDemo {
@State isSwitchOn: boolean = true
@State isCheckboxOn: boolean = false
build() {
Row() {
Column() {
// 1. 开关样式
Row() {
Text('自动登录')
.fontSize(22)
.margin({ right: 100 })
Toggle({ type: ToggleType.Switch, isOn: this.isSwitchOn })
.selectedColor(Color.Green)
.onChange((isOn) => {
this.isSwitchOn = isOn
})
}
.width(320)
.margin({ bottom: 30 })
// 2. 复选框样式
Row() {
Text('同意用户协议')
.fontSize(22)
.margin({ right: 80 })
Toggle({ type: ToggleType.Checkbox, isOn: this.isCheckboxOn })
.selectedColor(Color.Blue)
.onChange((isOn) => {
this.isCheckboxOn = isOn
})
}
.width(320)
}
.width('100%')
.alignItems(HorizontalAlign.Center)
}
.height('100%')
}
}
三、鸿蒙样式系统与资源管理
在第1篇中,我们直接在代码中硬编码了颜色、字体大小等(如fontSize(30)),这在实际开发中会导致维护困难 (比如要修改所有按钮颜色,得改几十处)。鸿蒙提供了资源系统 和样式复用机制来解决这个问题。
3.1 资源目录结构
鸿蒙的资源文件统一放在entry/src/main/resources目录下,分为基础资源 和限定资源:
resources/
├── base/ # 基础资源(所有设备通用)
│ ├── element/ # 元素资源(文本、颜色、尺寸等)
│ │ ├── color.json # 颜色资源
│ │ ├── float.json # 尺寸资源
│ │ └── string.json # 文本资源
│ ├── media/ # 媒体资源(图片、音频等)
│ │ └── logo.png
└── en_US/ # 限定资源(英文环境)
└── element/
└── string.json
3.2 资源定义与引用
3.2.1 定义资源
以颜色资源 和文本资源为例:
-
color.json(定义颜色):
json{ "color": [ { "name": "app_primary_color", "value": "#007DFF" // 应用主色调 }, { "name": "app_background_color", "value": "#F5F5F5" // 应用背景色 } ] } -
string.json(定义文本):
json{ "string": [ { "name": "app_name", "value": "个人信息编辑" }, { "name": "btn_save", "value": "保存信息" } ] }
3.2.2 引用资源
在ETS代码中,使用$r("资源类型.资源目录.资源名")引用资源:
ets
// 引用颜色
.backgroundColor($r("app.color.app_primary_color"))
// 引用文本
.Text($r("app.string.app_name"))
// 引用媒体
.Image($r("app.media.logo"))
3.3 样式复用机制
鸿蒙提供了**@Styles和@Extend**两种样式复用方式,减少重复代码。
3.3.1 @Styles------组件内样式复用
@Styles用于单个组件内的样式复用,比如定义一个通用的按钮样式:
ets
@Entry
@Component
struct StyleDemo {
// 定义按钮通用样式
@Styles buttonStyle() {
.fontSize(20)
.width(320)
.height(55)
.backgroundColor($r("app.color.app_primary_color"))
.borderRadius(8)
}
build() {
Row() {
Column() {
// 复用按钮样式
Button('保存')
.buttonStyle()
.margin({ bottom: 20 })
// 复用按钮样式并覆盖背景色
Button('取消')
.buttonStyle()
.backgroundColor(Color.Gray)
}
.width('100%')
.alignItems(HorizontalAlign.Center)
}
.height('100%')
}
}
3.3.2 @Extend------全局样式复用
@Extend用于全局的样式复用,比如定义所有Text组件的标题样式:
ets
// 定义全局Text组件的标题样式
@Extend(Text) function titleStyle() {
.fontSize(28)
.fontColor(Color.Black)
.fontWeight(FontWeight.Bold)
}
@Entry
@Component
struct ExtendDemo {
build() {
Row() {
Column() {
// 直接使用全局标题样式
Text('个人信息')
.titleStyle()
.margin({ bottom: 30 })
Text('联系方式')
.titleStyle() // 复用同一样式
}
.width('100%')
.alignItems(HorizontalAlign.Center)
}
.height('100%')
}
}
四、实战:开发「个人信息编辑页」
现在,我们将整合核心组件 、资源系统 和样式复用,开发一个完整的「个人信息编辑页」。
4.1 需求分析
- 显示用户头像(支持本地图片)
- 姓名输入框(实时监听输入)
- 性别选择(开关切换:男/女)
- 年龄输入框(限制数字输入)
- 保存按钮(点击后打印用户信息)
4.2 资源准备
-
将头像图片
avatar.png放入resources/base/media目录 -
在
color.json中定义颜色:json"color": [ {"name": "primary", "value": "#007DFF"}, {"name": "bg", "value": "#FFFFFF"}, {"name": "input_bg", "value": "#F5F5F5"} ] -
在
string.json中定义文本:json"string": [ {"name": "title", "value": "个人信息编辑"}, {"name": "label_name", "value": "姓名:"}, {"name": "label_gender", "value": "性别:"}, {"name": "label_age", "value": "年龄:"}, {"name": "btn_save", "value": "保存信息"} ]
4.3 完整代码实现
⌨️ 「个人信息编辑页」完整代码:
ets
import { InputType, ToggleType, Color } from '@ohos/arkui';
// 全局样式:标题样式
@Extend(Text) function pageTitle() {
.fontSize(32)
.fontColor(Color.Black)
.fontWeight(FontWeight.Bold)
.margin({ top: 40, bottom: 50 })
}
// 全局样式:输入框标签样式
@Extend(Text) function inputLabel() {
.fontSize(22)
.fontColor(Color.Black)
.width(100)
}
@Entry
@Component
struct UserInfoPage {
// 页面状态
@State avatar: string = $r("app.media.avatar")
@State name: string = "张三"
@State gender: boolean = true // true=男,false=女
@State age: string = "25"
build() {
// 页面根布局:垂直填充,背景色
Column({ space: 30 }) { // space:子组件间距
// 1. 页面标题
Text($r("app.string.title"))
.pageTitle()
// 2. 用户头像
Image(this.avatar)
.width(120)
.height(120)
.borderRadius(60) // 圆形头像
.objectFit(ImageFit.Cover)
.backgroundColor("#E0E0E0")
// 3. 姓名输入
Row() {
Text($r("app.string.label_name"))
.inputLabel()
TextField({ placeholder: "请输入姓名", text: this.name })
.fontSize(20)
.width(220)
.height(50)
.backgroundColor($r("app.color.input_bg"))
.borderRadius(6)
.padding({ left: 12 })
.onChange((value) => {
this.name = value
})
}
.width(320)
// 4. 性别选择
Row() {
Text($r("app.string.label_gender"))
.inputLabel()
Toggle({ type: ToggleType.Switch, isOn: this.gender })
.selectedColor($r("app.color.primary"))
.onChange((isOn) => {
this.gender = isOn
})
Text(this.gender ? "男" : "女") // 实时显示性别
.fontSize(20)
.margin({ left: 15 })
}
.width(320)
// 5. 年龄输入
Row() {
Text($r("app.string.label_age"))
.inputLabel()
TextField({ placeholder: "请输入年龄", text: this.age })
.type(InputType.Number) // 仅允许数字输入
.fontSize(20)
.width(220)
.height(50)
.backgroundColor($r("app.color.input_bg"))
.borderRadius(6)
.padding({ left: 12 })
.onChange((value) => {
this.age = value
})
}
.width(320)
// 6. 保存按钮
Button($r("app.string.btn_save"))
.fontSize(22)
.width(320)
.height(55)
.backgroundColor($r("app.color.primary"))
.borderRadius(8)
.margin({ top: 40 })
.onClick(() => {
// 点击保存,打印用户信息
const userInfo = {
name: this.name,
gender: this.gender ? "男" : "女",
age: this.age
}
console.log("✅ 保存的用户信息:", JSON.stringify(userInfo))
})
}
// 根布局样式:垂直居中,占满屏幕宽度,背景色
.width('100%')
.alignItems(HorizontalAlign.Center)
.backgroundColor($r("app.color.bg"))
.flexGrow(1) // 填充剩余高度
}
}
4.4 运行效果
✅ 页面顶部显示标题「个人信息编辑」
✅ 中间显示圆形头像,下方依次是姓名、性别、年龄输入项
✅ 性别切换开关,点击后实时显示男/女
✅ 年龄输入框仅允许数字输入
✅ 点击保存按钮后,在Logcat中打印完整的用户信息
五、UI开发常见问题与解决方案
5.1 组件不居中显示
- 原因:未正确配置容器组件的对齐方式
- 解决方案 :在父容器(Row/Column)中添加
alignItems(HorizontalAlign.Center)(水平居中)或justifyContent(FlexAlign.Center)(垂直居中)
5.2 网络图片加载失败
- 原因:未声明INTERNET权限或图片URL无效
- 解决方案:在config.json中声明网络权限,检查URL是否可访问
5.3 资源引用报错「Resource not found」
- 原因:资源key写错或资源文件放在错误的目录
- 解决方案:检查资源key是否与json文件中的name一致,确保资源放在base/element或base/media目录
5.4 组件宽高自适应失败
- 原因:未设置width/height或使用了固定值
- 解决方案 :使用百分比宽度(如
width('100%'))或flex布局(如flexGrow(1))
六、课后练习
- 给「个人信息编辑页」添加邮箱输入框,并限制输入格式(需包含@符号)
- 将头像改为网络图片加载(需加INTERNET权限)
- 给保存按钮添加加载状态(点击后显示"保存中..."并禁用按钮)
- 使用@Styles定义输入框的通用样式,减少重复代码
💡 答案提示 :邮箱格式验证可以在onChange事件中使用正则表达式/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/
七、总结与后续学习规划
7.1 本次学习成果
✅ 掌握了鸿蒙5大核心UI组件的高级用法
✅ 学会了用资源系统管理文本、颜色、尺寸
✅ 理解了@Styles和@Extend的样式复用机制
✅ 开发了完整的「个人信息编辑页」,整合了所有UI基础知识点
7.2 后续学习路径
第3篇 :鸿蒙布局系统(Row/Column/List/Grid等容器组件)------解决复杂界面的布局问题
第4篇 :鸿蒙状态管理(@State/@Prop/@Link)------处理组件间的数据传递
第5篇:鸿蒙数据存储(Preferences/RelationalStore)------实现数据持久化
7.3 学习建议
- 多写多改:把「个人信息编辑页」改造成「登录注册页」「商品详情页」等不同场景
- 阅读组件文档 :鸿蒙UI组件文档是最权威的学习资料
- 关注细节:比如组件的margin/padding、背景色、圆角等,这些细节决定了UI的美观度
下一篇,我们将学习鸿蒙的布局系统,解决复杂界面的排列问题!🚀