🎨 第8章 声明式 UI 开发
ArkUI 采用声明式范式 —— 你只需描述"UI 长什么样",框架自动处理"如何更新"。本章介绍核心布局容器与基础 UI 组件,让你快速构建漂亮界面。
9.1 8.1 声明式 vs 命令式
❌ 命令式(传统)
// 手动创建和更新 DOM
let btn = document.createElement('button');
btn.textContent = "点击 0";
btn.addEventListener('click', ()=>{
count++;
btn.textContent = `点击 ${count}`;
});
⚠️ 需要手动管理 UI 状态
✅ 声明式(ArkUI)
// 描述 UI 的样子,框架自动更新
@State count: number = 0;
Button(`点击 ${this.count}`)
.onClick(()=>{ this.count++; })
// count 变化 → UI 自动刷新!
✅ 只关心数据,UI 自动同步
9.2 8.2 基础布局容器
9.2.1 Column —— 纵向布局
Column({ space: 12 }) { // space: 子元素间距
Text("第一行")
Text("第二行")
Text("第三行")
}
.width('100%')
.padding(16)
.alignItems(HorizontalAlign.Center) // 水平对齐
.justifyContent(FlexAlign.SpaceBetween) // 垂直分布9.2.2 Row —— 横向布局
Row({ space: 8 }) {
Image($r('app.media.icon')).width(40)
Text("标题文字").fontSize(18).fontWeight(FontWeight.Bold)
Blank() // 弹性空白,把后面的元素推到右侧
Button("操作")
}
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')9.2.3 Stack —— 层叠布局
Stack({ alignContent: Alignment.Bottom }) {
// 背景层
Image($r('app.media.banner')).width('100%').height(200)
// 前景层(叠在上面)
Text("封面文字")
.fontColor('#FFFFFF')
.fontSize(24)
.padding({ bottom: 16 })
}🎮 布局容器可视化演示
点击上方按钮查看布局效果
9.3 8.3 基础 UI 组件
📝 Text
文本展示,支持富文本 TextSpan 嵌套
🖼️ Image
图片组件,支持网络/本地/资源图片
🔘 Button
按钮,三种类型:Capsule/Circle/Normal
✏️ TextInput
单行输入框,支持 onChange 事件
🔄 List / ForEach
列表渲染,ForEach 动态生成子组件
🃏 Swiper
轮播图,支持自动播放和指示器
📊 Progress
进度条,多种样式:Line/Ring/Eclipse
🔁 Toggle
开关切换,三种样式:Switch/Checkbox/Button
9.3.1 Text 组件详解
Text("Hello ArkTS")
.fontSize(20) // 字号
.fontWeight(FontWeight.Bold) // 加粗
.fontColor('#C02020') // 字色
.fontFamily('HarmonyOS Sans') // 字体
.textAlign(TextAlign.Center) // 对齐
.lineHeight(28) // 行高
.maxLines(2) // 最多2行
.textOverflow({ overflow: TextOverflow.Ellipsis }) // 省略号
.decoration({ type: TextDecorationType.Underline }) // 下划线9.3.2 TextInput 与双向绑定
@Entry
@Component
struct LoginPage {
@State username: string = '';
@State password: string = '';
build() {
Column({ space: 16 }) {
TextInput({ placeholder: '请输入用户名' })
.onChange((v: string) => { this.username = v; })
.width('90%')
TextInput({ placeholder: '请输入密码' })
.type(InputType.Password) // 密码类型
.onChange((v: string) => { this.password = v; })
.width('90%')
Button('登录')
.width('90%')
.enabled(this.username.length > 0 && this.password.length >= 6)
.onClick(() => {
console.log(`登录:${this.username}`);
})
Text(`用户名长度:${this.username.length} 字符`)
.fontColor('#999').fontSize(12)
}
.padding(24)
}
}📱 登录表单模拟
登录页面模拟
🔐 HarmonyOS 登录
用户名长度:0 字符
9.4 8.4 ForEach 列表渲染
@Entry
@Component
struct TodoList {
@State todos: string[] = ['学ArkTS', '写Hello World', '发布App'];
@State newTodo: string = '';
build() {
Column() {
// 动态列表
ForEach(this.todos, (item: string, index: number) => {
Row({ space: 12 }) {
Text(`${index + 1}. ${item}`).fontSize(16).layoutWeight(1)
Button('删除')
.fontSize(12)
.backgroundColor('#ff6b6b')
.onClick(() => {
this.todos.splice(index, 1); // 修改数组 → 刷新 UI
})
}
.width('100%').padding({ left: 16, right: 16, top: 8 })
})
// 添加输入框
Row({ space: 8 }) {
TextInput({ placeholder: '添加新任务', text: this.newTodo })
.layoutWeight(1)
.onChange((v) => { this.newTodo = v; })
Button('添加').onClick(() => {
if (this.newTodo) {
this.todos.push(this.newTodo);
this.newTodo = '';
}
})
}.padding(16)
}
}
}📋 ForEach Todo 列表演示
9.5 8.5 条件渲染 if/else
@Entry
@Component
struct WeatherCard {
@State weather: string = 'sunny';
build() {
Column() {
if (this.weather === 'sunny') {
Text("☀️ 晴天,出门不用带伞").fontColor('#FF8C00')
} else if (this.weather === 'rainy') {
Text("🌧️ 下雨了,记得带伞").fontColor('#0369a1')
} else {
Text("☁️ 多云,天气不错").fontColor('#666')
}
// 条件属性
Button("切换天气")
.backgroundColor(this.weather === 'sunny' ? '#FF8C00' : '#0369a1')
.onClick(() => {
const weathers = ['sunny', 'rainy', 'cloudy'];
const idx = weathers.indexOf(this.weather);
this.weather = weathers[(idx + 1) % 3];
})
}
}
}9.6 8.6 章末:组件属性速查
| 属性方法 | 说明 | 示例 |
|---|---|---|
.width() / .height() |
尺寸 | .width('100%') / .height(56) |
.padding() / .margin() |
内外边距 | .padding({ top:8, left:16 }) |
.backgroundColor() |
背景色 | .backgroundColor('#C02020') |
.borderRadius() |
圆角 | .borderRadius(10) |
.fontSize() / .fontColor() |
文字 | .fontSize(16) |
.opacity() |
透明度 | .opacity(0.7) |
.visibility() |
可见性 | .visibility(Visibility.Hidden) |
.animation() |
动画 | .animation({duration:300}) |
.onClick() / .onChange() |
事件 | .onClick(()=>{...}) |