鸿蒙UI开发基础——核心组件、样式系统与资源管理

鸿蒙UI开发基础------核心组件、样式系统与资源管理🖌️


学习目标

  1. 掌握鸿蒙5大核心UI组件(Text/Button/Image/TextField/Toggle)的高级用法
  2. 学会用鸿蒙资源系统管理文本、颜色、尺寸,避免硬编码
  3. 理解样式复用机制(@Styles/@Extend),写出可维护的UI代码
  4. 开发完整的「个人信息编辑页」,整合所有UI基础知识点
  5. 解决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 定义资源

颜色资源文本资源为例:

  1. color.json(定义颜色):

    json 复制代码
    {
      "color": [
        {
          "name": "app_primary_color",
          "value": "#007DFF"  // 应用主色调
        },
        {
          "name": "app_background_color",
          "value": "#F5F5F5"  // 应用背景色
        }
      ]
    }
  2. 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 资源准备

  1. 将头像图片avatar.png放入resources/base/media目录

  2. color.json中定义颜色:

    json 复制代码
    "color": [
      {"name": "primary", "value": "#007DFF"},
      {"name": "bg", "value": "#FFFFFF"},
      {"name": "input_bg", "value": "#F5F5F5"}
    ]
  3. 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)

六、课后练习

  1. 给「个人信息编辑页」添加邮箱输入框,并限制输入格式(需包含@符号)
  2. 将头像改为网络图片加载(需加INTERNET权限)
  3. 给保存按钮添加加载状态(点击后显示"保存中..."并禁用按钮)
  4. 使用@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的美观度

下一篇,我们将学习鸿蒙的布局系统,解决复杂界面的排列问题!🚀

相关推荐
梦想的旅途22 小时前
企业微信 API 触达外部群的深度实践(Java/Go/Python)
开发语言·python
Pcr_C2 小时前
Qt事件循环深度解析与实战指南
开发语言·c++·qt·开源
Fate_I_C2 小时前
Kotlin 中 `@JvmField` 注解的使用
android·开发语言·kotlin
大大祥2 小时前
一个kotlin实现的视频播放器
android·开发语言·kotlin·音视频
唐古乌梁海2 小时前
【pytest】pytest详解-入门到精通
开发语言·python·pytest
爱上妖精的尾巴2 小时前
7-1 WPS JS宏 Object对象创建的几种方法
开发语言·前端·javascript
ZePingPingZe2 小时前
静态代理、JDK和Cglib动态代理、回调
java·开发语言
2501_921649492 小时前
iTick 全球外汇、股票、期货、基金实时行情 API 接口文档详解
开发语言·python·websocket·金融·restful
你怎么知道我是队长2 小时前
python---进程
开发语言·chrome·python