第三章:ArkUI 组件与布局
本章配套代码:
code/Chapter3(组件与布局综合展示 + 手势交互案例) 前置要求:已完成第二章 ArkTS 基础语法
3.1 学习目标
- 掌握常用基础组件:Text、Button、TextInput、Toggle、Image
- 理解容器组件:Column、Row、Stack、Grid、Flex
- 掌握布局核心技巧:
layoutWeight、justifyContent、alignItems - 学会组件样式设置(尺寸、间距、圆角、阴影)
- 掌握 ArkUI 手势系统:常用手势(点击/长按/拖动/旋转/捏合/滑动)与组合手势(GestureGroup)
3.2 ArkUI 概述
ArkUI 是 HarmonyOS 的声明式 UI 框架。核心思想:UI 由数据驱动,状态变化自动刷新界面。
┌─────────────────────────────────────────────┐
│ ArkUI 声明式范式 │
│ │
│ 状态(@Local 等)──> build()──> UI组件树 │
│ │ │ │
│ └──────── 事件回调 ◄───┘ │
│ (onClick/onChange...) │
└─────────────────────────────────────────────┘
三大核心概念:
| 概念 | 说明 | 示例 |
|---|---|---|
| 组件 | UI 的基本单元 | Text、Button、Column |
| 属性 | 配置组件外观行为 | .fontSize()、.backgroundColor() |
| 事件 | 响应用户交互 | .onClick()、.onChange() |
Text 文本
Text(value: string | Resource)文本组件,用于展示文字内容。
Text('人生若只如初见')
.fontSize(20) // 字号
.fontWeight(FontWeight.Bold) // 字重
.fontColor('#333333') // 颜色
.textAlign(TextAlign.Center) // 对齐
.maxLines(2) // 最大行数
.textOverflow({ overflow: TextOverflow.Ellipsis }) // 超长省略
| 属性 | 说明 |
|---|---|
fontSize | 字体大小(vp) |
fontWeight | 字体粗细(Regular/Bold/...) |
fontColor | 字体颜色 |
textAlign | 文本对齐 |
maxLines | 最大行数 |
textOverflow | 溢出处理(Ellipsis 省略号) |
多段文本 Span 子组件:
Text() {
Span('重点:').fontColor('#ff6b6b').fontWeight(FontWeight.Bold)
Span('普通内容').fontColor('#333333')
}
.fontSize(16)
Button 按钮
Button(label: ResourceStr, options?: ButtonOptions)按钮组件,用于响应用户点击。
Button('点击')
.type(ButtonType.Capsule) // 胶囊样式
.width(120)
.height(40)
.fontSize(16)
.backgroundColor('#007DFF')
.onClick(() => {
console.log('clicked')
})
ButtonType 类型:
| 类型 | 外观 | 说明 |
|---|---|---|
ButtonType.Capsule | 胶囊形 | 圆角最大,不支持 borderRadius |
ButtonType.Circle | 圆形 | 不支持 borderRadius |
ButtonType.Normal | 直角矩形 | 支持 borderRadius 自定义圆角 |
💡 可内嵌子组件:
Button() { Row() { Image(...); Text(...) } },实现"图标+文字"按钮。
TextInput 输入框
TextInput(value?: { placeholder?: ResourceStr, text?: ResourceStr, controller?: TextInputController })单行文本输入框。
TextInput({ placeholder: '请输入内容', text: this.inputText })
.width(200)
.height(44)
.type(InputType.Normal) // 输入类型:Normal/Password/Number...
.maxLength(20)
.onChange((value: string) => {
this.inputText = value
})
| 属性 | 说明 |
|---|---|
placeholder | 占位提示文字 |
text | 初始内容 |
type | 输入类型(密码/数字/邮箱等) |
maxLength | 最大长度 |
onChange | 内容变化回调 |
InputType 常用类型: Normal(普通)、Password(密码)、Number(数字)、Email(邮箱)。
小案例:密码可见性切换
@Local showPassword: boolean = false
TextInput({ placeholder: '请输入密码' })
.type(this.showPassword ? InputType.Normal : InputType.Password)
.onChange((v: string) => { /* 处理密码 */ })
Button(this.showPassword ? '隐藏' : '显示')
.onClick(() => { this.showPassword = !this.showPassword })
Toggle 开关
Toggle(options: { type: ToggleType, isOn?: boolean })开关/复选/状态按钮组件。
Toggle({ type: ToggleType.Switch, isOn: this.isOn })
.onChange((isOn: boolean) => {
this.isOn = isOn
})
ToggleType: Switch(开关)、Checkbox(复选)、Radio(单选)、Button(状态按钮)。
小案例:蓝牙开关
@Local bluetoothOn: boolean = false
Row() {
Text('Bluetooth')
Toggle({ type: ToggleType.Switch, isOn: this.bluetoothOn })
.onChange((isOn: boolean) => { this.bluetoothOn = isOn })
}
Image 图片
Image(src: PixelMap | ResourceStr | DrawableDescriptor)图片组件,支持本地、资源、网络、字节流。
Image($r('app.media.logo')) // 资源目录图片
.width(100)
.height(100)
.objectFit(ImageFit.Cover) // 填充方式
.borderRadius(10)
Image($rawfile('avatar.png')) // rawfile 目录图片
Image('https://example.com/x.png') // 网络图片(需网络权限)
图片来源:
| 方式 | 语法 | 说明 |
|---|---|---|
| 资源目录 | $r('app.media.xxx') | resources/base/media |
| rawfile | $rawfile('xxx.png') | resources/rawfile |
| 网络 | URL 字符串 | 需 INTERNET 权限 |
| 字节流 | Image(pixelMap) | 动态生成 |
💡
objectFit:Cover(裁剪填充)、Contain(完整显示)、Fill(拉伸填满)。
Column 垂直布局
Column({ space?: number | string })子组件沿垂直方向(主轴)排列的容器。
Column({ space: 10 }) { // space:子组件间距
Text('春眠不觉晓')
Text('处处闻啼鸟')
}
.width('100%')
.justifyContent(FlexAlign.Center) // 主轴对齐
.alignItems(HorizontalAlign.Center) // 交叉轴对齐
Row 水平布局
Row({ space?: number | string })子组件沿水平方向(主轴)排列的容器。
Row({ space: 10 }) {
Text('明月松间照')
Text('清泉石上流')
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween) // 两端对齐