第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 容器。在底部导航栏中可以看到 FlexAlign 和 layoutWeight 的运用:
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 })
maxLines 和 textOverflow 配合使用可以控制多行文本省略,这在标题卡片中非常实用。
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 })
GridRow的columns定义了不同断点下的列数:小屏 4 列,中屏 8 列,大屏 12 列GridCol的span定义了在不同断点下占几列- 断点根据设备宽度自动切换,无需手动监听
项目的响应式方法配合断点系统使用:
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 等组件使用