HarmonyOS APP《画伴梦工厂》开发第2篇:ArkUI 声明式 UI 基础——组件、布局与样式

第2篇:ArkUI 声明式 UI 基础------组件、布局与样式

系列 :HarmonyOS 从入门到实践 · 画伴梦工厂实战

难度 :⭐ 入门

前置知识 :1.1 ArkTS 语言入门

涉及源文件products/default/src/main/ets/pages/Index.ets


ArkUI 是 HarmonyOS 原生的声明式 UI 框架,采用组件化链式属性调用的编程范式。与传统的命令式 UI(如 Android XML + Java)不同,ArkUI 让开发者用简洁的 TypeScript/ArkTS 代码直接描述界面"长什么样",框架负责高效的渲染和刷新。

本文将以"画伴梦工厂"项目的首页代码为例,带你掌握 ArkUI 的核心概念。


一、四大布局容器

ArkUI 提供了 Column(纵向)、Row(横向)、Stack(堆叠)和 Flex(弹性)四种核心布局容器,它们也是 ArkUI 中最常用的布局组件。

1. Column:纵向排列

Column 容器将其子组件沿垂直方向依次排列。在 Index.ets 的 build() 方法中,整个页面最外层就是一个 Column:

typescript 复制代码
build() {
  Column() {
    // 子组件依次纵向排列
    this.CurrentPage()
  }
  .width('100%')
  .height('100%')
  .backgroundColor(this.softBackground)
}

Column 通过链式调用设置自己的宽高(width/height)和背景色(backgroundColor)。几乎所有 UI 组件都支持这种链式调用,这是 ArkUI 声明式语法的标志性特征。

2. Row:横向排列

Row 容器将子组件水平排列。页面的顶部导航栏区域使用了 Row + Column 的组合:

typescript 复制代码
Row() {
  Column() {
    Text('把画变动画')
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .fontColor(this.ink)
    Text('拍一张画,或从相册里选一张')
      .fontSize(12)
      .fontColor('#68708A')
      .margin({ top: 4 })
  }
  .alignItems(HorizontalAlign.Start)
}
.width('100%')
.height(56)
.padding({ left: this.pageEdge(), right: this.pageEdge() })

这里 Row 负责左右布局,内部嵌了一个 Column 来垂直排列两行文字。alignItems(HorizontalAlign.Start) 让第二列的文字左对齐,padding 设置内边距。

3. Stack:堆叠布局

Stack 允许子组件在 z 轴上层叠,常用于在图片上方叠加文字或按钮。项目首页的 Hero Banner 就是用 Stack 实现的:

typescript 复制代码
Stack({ alignContent: Alignment.TopStart }) {
  Image($r('app.media.hero_portal'))
    .width('100%')
    .height(this.heroHeight())
    .objectFit(ImageFit.Cover)
    .borderRadius(26)
  Column() {
    Text('让孩子笔下的')
      .fontSize(this.heroTitleSize())
      .fontWeight(FontWeight.Bold)
      .fontColor('#FFFFFF')
    Text('画作活起来')
      .fontSize(this.heroTitleSize())
      .fontWeight(FontWeight.Bold)
      .fontColor('#FFFFFF')
      .margin({ top: 4 })
      .textAlign(TextAlign.Start)
  }
  .alignItems(HorizontalAlign.Start)
  .padding({ left: this.pageEdge(), top: 26 })
}

alignContent: Alignment.TopStart 指定了子组件在 Stack 中默认从左上角开始定位,文字列通过 padding 调整在图片上的位置。

4. Flex 弹性布局(Row 的底层实现)

虽然项目中大量使用 Row,但 Row 本质上是一种 Flex 容器。在底部导航栏中可以看到 FlexAlignlayoutWeight 的运用:

typescript 复制代码
Row() {
  this.NavIcon(0)
  this.NavIcon(1)
  this.NavIcon(2)
}
.width('100%')
.height(54)
.padding({ left: 6, right: 6, top: 4, bottom: 4 })

其中每个 NavIcon 设置了 layoutWeight(1),实现了三等分的效果,等价于 Flex 中的 flex: 1


二、基础组件

Text(文本)

Text 是使用频率最高的组件。项目中有丰富的 Text 使用示例:

typescript 复制代码
// 标题文字
Text('把画变动画')
  .fontSize(20)
  .fontWeight(FontWeight.Bold)
  .fontColor(this.ink)

// 描述文字,带溢出省略
Text('拍一张画,或从相册里选一张')
  .fontSize(12)
  .fontColor('#68708A')
  .margin({ top: 4 })
  .maxLines(1)
  .textOverflow({ overflow: TextOverflow.Ellipsis })

maxLinestextOverflow 配合使用可以控制多行文本省略,这在标题卡片中非常实用。

Image(图片)

Image 组件用于加载图片资源,支持 $r() 引用资源目录下的图片,也支持网络 URL:

typescript 复制代码
// 通过 $r 引用 media 资源
Image($r('app.media.hero_portal'))
  .width('100%')
  .height(this.heroHeight())
  .objectFit(ImageFit.Cover)
  .borderRadius(26)

// 通过 $rawfile 引用 rawfile 目录下的文件
Image($rawfile('index/demo1.png'))

objectFit(ImageFit.Cover) 让图片等比例缩放并裁剪以填满容器,类似 CSS 的 object-fit: cover

Button(按钮)

Button 组件支持文字按钮和自定义内容按钮:

typescript 复制代码
// 文字按钮
Button('查看视频结果')
  .width('90%')
  .height(46)
  .fontSize(15)
  .fontWeight(FontWeight.Bold)
  .fontColor('#FFFFFF')
  .backgroundColor(this.brandPurple)
  .borderRadius(23)
  .onClick(() => {
    // 点击处理逻辑
  })

borderRadius(23) 配合高度 46 实现了圆角胶囊按钮的效果。

TextInput(文本输入)

TextInput 用于用户文字输入,支持占位符和双向绑定:

typescript 复制代码
TextInput({ placeholder: '说出角色、场景和故事', text: this.chatInput })
  .height(42)
  .fontSize(13)
  .fontColor(this.ink)
  .backgroundColor('#F7F7FB')
  .borderRadius(21)
  .onChange((value: string) => {
    this.chatInput = value;
  })

onChange 回调接收用户输入的新值,项目中配合 @State chatInput 实现数据绑定。

Progress(进度条)

Progress 组件可展示线性或环形进度:

typescript 复制代码
Progress({ value: this.progress, total: 100, type: ProgressType.Linear })
  .width('84%')
  .height(8)
  .color(this.mint)
  .backgroundColor('#ECECF6')
  .borderRadius(6)

ProgressType.Linear 为线性进度条,color 设置已完成部分的颜色。


三、链式属性调用(Builder Pattern)

ArkUI 最鲜明的语法特征就是链式调用 。每个组件创建后,可以通过 . 连续设置多个属性,代码可读性极强:

typescript 复制代码
Text('拍一张画,或从相册里选一张')
  .fontSize(12)            // 字号
  .fontColor('#68708A')    // 颜色
  .margin({ top: 4 })      // 外边距
  .maxLines(1)             // 最大行数
  .textOverflow({ overflow: TextOverflow.Ellipsis })  // 溢出省略

从上到下的调用顺序清晰表达了组件的样式配置,阅读代码时可以快速了解 UI 的结构和样式。这也是声明式 UI 相比命令式 UI 的一大优势。


四、$r 资源引用

HarmonyOS 使用 $r()$rawfile() 两个全局函数来引用资源:

函数 用途 示例
$r('app.media.xxx') 引用 resources/base/media/ 下的资源 $r('app.media.hero_portal')
$r('app.string.xxx') 引用字符串资源 $r('app.string.app_name')
$rawfile('path/file') 引用 rawfile/ 目录下的原始文件 $rawfile('index/demo1.png')

项目中两者的使用场景:

typescript 复制代码
// 编译时资源($r 方式)
Image($r('app.media.hero_portal'))

// 原始文件($rawfile 方式)
Image($rawfile('index/demo1.png'))

$r 引用的资源在编译时会进行优化(如按屏幕密度选择不同分辨率的图片),而 $rawfile 则保持文件原样。


五、GridRow/GridCol:响应式网格

"画伴梦工厂"使用了 HarmonyOS 的响应式网格布局系统,让页面在不同屏幕尺寸(手机、平板、折叠屏)上自适应:

typescript 复制代码
GridRow({
  columns: { sm: 4, md: 8, lg: 12 }
}) {
  GridCol({
    span: { sm: 4, md: 8, lg: 12 }
  }) {
    Stack({ alignContent: Alignment.TopStart }) {
      // Hero Banner 内容
    }
    .width('100%')
    .height(this.heroHeight())
  }
}
.width(this.panelWidth())
.margin({ top: 10, bottom: 15 })
  • GridRowcolumns 定义了不同断点下的列数:小屏 4 列,中屏 8 列,大屏 12 列
  • GridColspan 定义了在不同断点下占几列
  • 断点根据设备宽度自动切换,无需手动监听

项目的响应式方法配合断点系统使用:

typescript 复制代码
private panelWidth(): string {
  return new BreakPointType<string>({ sm: '90%', md: '86%', lg: '74%', xl: '64%' })
    .getValue(this.currentBreakpoint);
}

六、Scroll 滚动容器

当内容超出屏幕时,Scroll 容器提供滚动能力。项目首页的"创作广场"区域使用水平滚动的 Scroll:

typescript 复制代码
Scroll() {
  Row() {
    ForEach(WORKS, (item: Artwork) => {
      this.WorkThumbnail(item)
    }, (item: Artwork) => item.id.toString())
  }
}
.scrollable(ScrollDirection.Horizontal)  // 设置滚动方向为水平
.scrollBar(BarState.Off)                  // 隐藏滚动条
.height(126)

滚动配置的要点:

  • scrollable 指定滚动方向(水平/垂直/双向)
  • scrollBar 控制滚动条显示(On/Off/Auto)
  • Scroll 容器有一个子组件,内容超出时自动滚动

项目中"创作广场"的每个作品缩略图:

typescript 复制代码
@Builder
private WorkThumbnail(item: Artwork) {
  Column() {
    Stack() {
      Rect().width(96).height(78).fill(item.color).radius(12)
      Image(this.getWorkCover(item.id - 1))
        .width(96).height(78).objectFit(ImageFit.Cover).borderRadius(12)
    }
    Text(item.title)
      .fontSize(11)
      .fontColor(this.ink)
      .maxLines(1)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
      .margin({ top: 8 })
  }
  .width(104)
  .margin({ right: 10 })
  .onClick(() => {
    this.selectWork(item.id - 1);
    this.openWorksSecondary();
  })
}

七、实际页面综合示例

将以上知识综合起来,来看 Index.ets 中 HomePage 页面的整体结构:

复制代码
Column (全屏,纵向排列)
  ├── GridRow (响应式网格容器)
  │   └── GridCol
  │       └── Stack (Hero Banner:背景图 + 叠加文字)
  │           ├── Image (背景图)
  │           └── Column (文字层)
  ├── SectionTitle ("创作广场")
  ├── Scroll (水平滚动)
  │   └── Row
  │       └── ForEach → WorkThumbnail × N
  ├── SectionTitle ("今日灵感")
  └── InspirationPanel

完整的 build 方法代码:

typescript 复制代码
build() {
  Stack({ alignContent: Alignment.Bottom }) {
    Column() {
      this.CurrentPage()
    }
    .width('100%')
    .height('100%')
    .backgroundColor(this.softBackground)
    this.BottomBar()
  }
  .width('100%')
  .height('100%')
  .backgroundColor(this.softBackground)
}

最外层使用 Stack,将页面内容和底部导航栏层叠在一起;页面内容填充全屏,底部导航栏浮动在底部。这种结构既是 ArkUI 典型的页面布局模式,也展示了 Stack 在页面架构中的妙用。


总结

本文通过"画伴梦工厂"的真实项目代码,介绍了 ArkUI 声明式 UI 的六大核心概念:

概念 关键点
布局容器 Column(纵向)、Row(横向)、Stack(堆叠)、Flex(弹性)
基础组件 Text、Image、Button、TextInput、Progress
链式调用 通过 .属性() 连续配置组件样式
资源引用 $r() 引用媒体/字符串资源,$rawfile() 引用原始文件
响应式网格 GridRow/GridCol 配合断点实现多屏适配
滚动容器 Scroll 配合 scrollable 控制滚动方向

下一篇: 第 1.3 篇将深入探讨 @State 与状态管理------如何让页面随数据变化自动刷新,实现真正的"响应式 UI"。


参考源码

本文所有代码均来自项目文件:

  • products/default/src/main/ets/pages/Index.ets --- 首页主页面,包含 Column/Row/Stack 布局、Scroll 滚动、GridRow/GridCol 网格、Text/Image/Button/TextInput 等组件使用