9  第8章 声明式 UI 开发

🎨 第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(()=>{...})