SDK 版本:HarmonyOS NEXT 6.1.1(API 24)
语言框架:ArkTS(声明式 UI)
核心组件:Column、Flex、Scroll、ForEach
源码文件:ColumnFlexDemo.ets




一、引言
在 HarmonyOS NEXT 的原生 ArkTS 开发中,布局是构建用户界面的基石。几乎所有页面都需要一个主方向来组织内容,而 Column 组件就是 ArkTS 提供的最核心的纵向布局容器。然而,在实际开发中,单纯的纵向排列远远不够——我们常常需要在一个纵向页面中嵌入横排的区域、换行的标签流、甚至复杂的网格结构。这就引出了本节的核心主题:Column 弹性布局 + Flex 混合方向排列。
本文将从最基础的概念入手,深入剖析 Column 与 Flex 的协同工作原理,结合完整的示例代码,带你掌握从简单到复杂的混合方向布局技巧。无论你是刚接触 HarmonyOS 开发的新手,还是有经验的 ArkUI 开发者,都能从中获得实用的布局策略和最佳实践。
二、基础概念与原理
2.1 什么是 Column?
Column 是 ArkTS 中最基本的纵向布局容器。它的核心特性是:
- 主轴(Main Axis):垂直方向(从上到下)
- 交叉轴(Cross Axis):水平方向(从左到右)
- 子元素排列:默认沿主轴依次排列
基本语法:
Column({ space: number }) {
// 子组件列表
}
其中 space 参数控制子元素之间的间距,单位是 vp(虚拟像素)。
Column 的核心属性
| space | number | 子元素之间的间距(vp) |
| alignItems | HorizontalAlign | 交叉轴(水平方向)的对齐方式 |
| justifyContent | FlexAlign | 主轴(垂直方向)的对齐方式 |
| width | Length | 容器宽度 |
| height | Length | 容器高度 |
| padding | Padding | 内边距 |
| constraintSize | ConstraintSizeOptions | 约束尺寸(最小/最大宽高) |
alignItems 对齐选项
enum HorizontalAlign {
Start = 0, // 左对齐
Center = 1, // 居中对齐
End = 2 // 右对齐
}
justifyContent 对齐选项
enum FlexAlign {
Start = 0, // 顶部对齐
Center = 1, // 居中对齐
End = 2, // 底部对齐
SpaceBetween = 3, // 两端对齐(子元素间均分空间)
SpaceAround = 4, // 环绕分布
SpaceEvenly = 5 // 均匀分布
}
2.2 什么是 Flex?
Flex 是 ArkTS 提供的弹性布局容器,类似于 CSS Flexbox。它可以控制子元素在一行或一列中的排列方式,支持换行、比例分配等高级特性。
基本语法:
Flex({
direction?: FlexDirection,
wrap?: FlexWrap,
justifyContent?: FlexAlign,
alignItems?: ItemAlign,
alignContent?: FlexAlign
}) {
// 子组件列表
}
Flex 的关键参数
| direction | FlexDirection | 主轴方向(Row 水平 / Column 垂直) |
| wrap | FlexWrap | 是否换行(NoWrap / Wrap) |
| justifyContent | FlexAlign | 主轴对齐方式 |
| alignItems | ItemAlign | 交叉轴对齐方式 |
| alignContent | FlexAlign | 多行时的对齐方式 |
FlexDirection 枚举
enum FlexDirection {
Row = 0, // 水平方向(左→右)
Column = 1, // 垂直方向(上→下)
RowReverse = 2, // 水平反向(右→左)
ColumnReverse = 3 // 垂直反向(下→上)
}
FlexWrap 枚举
enum FlexWrap {
NoWrap = 0, // 不换行(默认)
Wrap = 1 // 自动换行
}
ItemAlign 枚举
enum ItemAlign {
Auto = 0, // 自动
Start = 1, // 对齐到起点
Center = 2, // 居中对齐
End = 3, // 对齐到终点
Stretch = 4, // 拉伸填满
Baseline = 5 // 基线对齐
}
2.3 混合方向排列的核心思想
「Column 弹性布局 + Flex 混合方向排列」 的核心思想是:
Column(纵向主轴)→ 每个元素区域 → Flex(横向/弹性排列)→ 子元素
具体来说:
这种模式的优势在于:
- 结构清晰:页面布局层次一目了然,Column 管纵向、Flex 管横向
- 组合灵活:可以轻松嵌入多种排列方式,如居中、两端对齐、换行标签等
- 响应式好:Flex 的弹性特性使得布局能自适应不同屏幕尺寸
- 性能优秀:避免了多层嵌套带来的性能开销
三、环境搭建
3.1 创建项目
使用 DevEco Studio 创建一个新的 HarmonyOS 项目:
3.2 项目结构
创建完成后,项目的主要目录结构如下:
ColumnFlexDemo/
├── entry/
│ └── src/
│ └── main/
│ ├── ets/
│ │ ├── entryability/
│ │ │ └── EntryAbility.ets
│ │ ├── pages/
│ │ │ ├── Index.ets // 首页入口
│ │ │ └── ColumnFlexDemo.ets // 本文的主演示页
│ │ └── resources/
│ └── resources/
│ ├── base/
│ │ ├── element/
│ │ ├── media/
│ │ └── profile/
│ └── en_US/
│ └── element/
└── build-profile.json5
3.3 修改 Index.ets
为了让用户可以导航到演示页,我们需要在 Index.ets 中添加一个入口按钮:
// entry/src/main/ets/pages/Index.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct Index {
build() {
Column() {
// … 其他导航按钮 …
Button('🧩 Column + Flex 混合排列')
.width('80%')
.height(48)
.backgroundColor('#667eea')
.borderRadius(24)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 16 })
.onClick(() => {
router.pushUrl({ url: 'pages/ColumnFlexDemo' });
})
}
.width('100%')
.height('100%')
.backgroundColor('#0A0A1A')
}
}
四、完整示例代码解析
4.1 文件头与导入语句
/**
* Column + Flex 混合方向排列演示页
*
* 📐 布局要点:Column 内嵌 Flex,实现"纵向主轴 + 横向子区"的混合方向排列
*
* 适用场景:
* – 卡片式信息展示(头像横向 + 描述纵向)
* – 表单布局(标签横向 + 内容纵向)
* – Dashboard 仪表盘(混合排版)
*
* 🛠 核心 API:
* – Column({ space }) —— 纵向主轴容器
* – Flex({ direction: FlexDirection.Row, … }) —— 横向弹性容器
* – FlexWrap.Wrap —— 子项自动换行
*/
import { router } from '@kit.ArkUI';
关键说明:
- 文件头部的注释块不仅是对代码的说明,也是对外展示开发思路的文档
- import { router } from '@kit.ArkUI' 是 HarmonyOS NEXT 推荐的包导入方式(@kit.ArkUI 是 ArkUI 套件的统一入口)
- 与旧版本 import router from '@ohos.router' 相比,@kit.ArkUI 提供了更简洁的模块导入体验
4.2 @Entry 与 @Component 装饰器
@Entry
@Component
struct ColumnFlexDemo {
build() {
// 布局内容…
}
}
装饰器说明:
- @Entry:标记该组件为页面的入口点。每个页面有且仅有一个 @Entry,它告诉框架这是一个独立的路由目标
- @Component:声明这是一个自定义组件。ArkTS 中所有 UI 组件必须是 struct 类型,且必须实现 build() 方法
- build() 方法:描述 UI 结构的声明式方法,在此方法中通过链式调用构建组件树
需要注意的是,ArkTS 中的 struct 是值类型,与 TypeScript 的 class 不同。值类型意味着组件实例在状态变化时会更高效地进行局部更新。
4.3 外层结构:Scroll + Column
build() {
Scroll() {
Column() {
// 页面标题
// 说明卡片
// 演示区域
// 综合示例
// …
}
.width('100%')
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height('100%')
.backgroundColor('#0A0A1A')
}
设计思路:
- 为什么用 Scroll 包裹? 演示页面内容较多,超过一屏高度。Scroll 组件提供了垂直滚动能力,确保所有内容可见
- Column 的 alignItems(HorizontalAlign.Center):让 Column 内的所有子元素在水平方向上居中对齐,这样每个演示区块都会自动居中显示,无需为每个子元素单独设置对齐
这里有一个重要的布局经验:当你需要「整体居中、局部滚动」的页面时,Scroll > Column > 内容 是最常用的三层结构。Scroll 负责滚动行为,Column 负责纵向排列,两者分工明确。
4.4 页面标题
Text('🧩 Column + Flex 混合排列')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.margin({ top: 48, bottom: 8 })
Text('Column 纵向主轴内嵌 Flex 横向排列 — 一处主轴、多处次轴')
.fontSize(14)
.fontColor('#AAAAAA')
.textAlign(TextAlign.Center)
.width('85%')
.lineHeight(20)
.margin({ bottom: 24 })
注意点:
- 标题使用 margin({ top: 48, bottom: 8 }) 留出顶部的状态栏空间和底部间距
- 副标题使用 width('85%') 限制宽度,让文本不会撑满整屏,视觉上更优雅
- lineHeight(20) 设置行高,改善多行文本的可读性
4.5 布局结构说明卡片
Column() {
Text('📐 布局结构')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#667eea')
.width('100%')
.margin({ bottom: 8 })
Text('Column(纵向主轴)→ Flex(横向弹性行)→ 子元素')
.fontSize(13)
.fontColor('#CCCCCC')
.width('100%')
Text('Column 是整个页面的纵向容器,保证所有块从上到下排列。')
.fontSize(12)
.fontColor('#888888')
.width('100%')
.lineHeight(18)
.margin({ top: 8 })
Text('在每个区块内部,使用 Flex(FlexDirection.Row) 实现水平排列,支持换行、居中等弹性布局能力。')
.fontSize(12)
.fontColor('#888888')
.width('100%')
.lineHeight(18)
.margin({ top: 4 })
}
.width('85%')
.padding(16)
.backgroundColor('#1A1A2E')
.borderRadius(12)
.margin({ bottom: 20 })
设计要点:
- 使用卡片(Card) 样式将说明文字包裹起来,与背景区分
- .backgroundColor('#1A1A2E') 使用深蓝紫色,与页面整体的深色主题协调
- .borderRadius(12) 设置圆角,让卡片更柔和
- 说明文字使用不同颜色区分层次:标题亮色、关键路径中等色、详细描述浅色
这种「卡片式说明」是 ArkTS 布局中非常常用的设计模式,尤其适合教学演示或 Dashboard 页面。
五、四大演示场景详解
5.1 演示一:横向 Flex 居中
Column() {
Text('▶ 横向 Flex(居中)')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#EEEEEE')
.width('100%')
.margin({ bottom: 8 })
// === 关键布局:Column 套 Flex ===
// Column 提供垂直方向的空间
// Flex(FlexDirection.Row) 在其内部做水平排列
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center }) {
ForEach(['🏠 首页', '📁 项目', '📄 文档', '⚙️ 设置'], (item: string, index: number) => {
Text(item)
.fontSize(13)
.fontColor('#FFFFFF')
.backgroundColor(index % 2 === 0 ? '#667eea' : '#764ba2')
.width(70)
.height(36)
.textAlign(TextAlign.Center)
.borderRadius(8)
.margin(4)
})
}
.width('100%')
.padding(8)
.backgroundColor('#2A2A4A')
.borderRadius(8)
}
.width('100%')
.margin({ bottom: 16 })
布局层次分析:
Column (最外层 — 演示区块容器)
├── Text (区块标题)
└── Flex (方向: Row, 对齐: Center)
├── Text (🏠 首页)
├── Text (📁 项目)
├── Text (📄 文档)
└── Text (⚙️ 设置)
核心知识点:
Flex 的 justifyContent: FlexAlign.Center:让所有子元素在水平方向上整体居中。这类似 CSS 的 justify-content: center
ForEach 的第二个参数类型标注:(item: string, index: number) => { … }。在 ArkTS 中,ForEach 的回调函数需要显式标注参数类型(string 和 number),这是 ArkTS 静态类型检查的要求
按奇偶索引交替背景色:index % 2 === 0 ? '#667eea' : '#764ba2' 实现了简单的斑马条纹效果,让相邻按钮视觉上区分开来
.margin(4) 简写:当上下左右边距相同时,可以简写为一个数值
运行效果:四个按钮在灰色背景容器中水平均匀居中排列,按钮交替显示紫色和蓝紫色,视觉层次分明。
5.2 演示二:横向 Flex 两端对齐
Column() {
Text('▶ 横向 Flex(两端对齐)')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#EEEEEE')
.width('100%')
.margin({ bottom: 8 })
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(['❄️ 功能 A', '🔥 功能 B', '💧 功能 C'], (item: string) => {
Text(item)
.fontSize(13)
.fontColor('#FFFFFF')
.backgroundColor('#2ECC71')
.width(90)
.height(40)
.textAlign(TextAlign.Center)
.borderRadius(8)
})
}
.width('100%')
.padding(8)
.backgroundColor('#2A2A4A')
.borderRadius(8)
}
.width('100%')
.margin({ bottom: 16 })
布局层次分析:
Column (最外层)
├── Text (区块标题)
└── Flex (方向: Row, 对齐: SpaceBetween)
├── Text (❄️ 功能 A)
├── Text (🔥 功能 B)
└── Text (💧 功能 C)
核心知识点:
FlexAlign.SpaceBetween:子元素两端对齐,第一个元素紧贴容器左边缘,最后一个紧贴右边缘,中间元素均分剩余空间。这是导航栏和功能入口的经典布局
width(90) 固定宽度:与演示一不同,这里每个按钮明确设置了 90vp 的宽度。SpaceBetween 配合固定宽度可以精确控制每个元素占用的空间
统一绿色调背景:所有按钮使用 #2ECC71(翠绿色),强调这组功能属于同一类别
运行效果:三个绿色功能按钮在容器中均匀分布,左右两端各顶一个按钮,中间的按钮位于正中间。
对比分析:Center vs SpaceBetween
| Center | 整体居中 | 导航菜单、标签栏 |
| SpaceBetween | 两端对齐 | 功能入口、工具栏 |
| SpaceAround | 环绕分布 | 等间距图标 |
| SpaceEvenly | 均匀分布 | 页脚链接、分页控件 |
5.3 演示三:Flex 自动换行(Wrap)
Column() {
Text('▶ 横向 Flex(自动换行)')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#EEEEEE')
.width('100%')
.margin({ bottom: 8 })
// FlexWrap.Wrap 让子项超出行宽时自动折行
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(
['标签1', '标签2', '标签很长三', '标签4', '标签5', '标签六六六', '标签7', '标签8', '标签九九'],
(item: string) => {
Text(item)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#E67E22')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(12)
.margin(4)
}
)
}
.width('100%')
.padding(8)
.backgroundColor('#2A2A4A')
.borderRadius(8)
}
.width('100%')
.margin({ bottom: 16 })
布局层次分析:
Column (最外层)
├── Text (区块标题)
└── Flex (方向: Row, 换行: Wrap, 对齐: Start)
├── Text (标签1) │─── 第一行 ───│
├── Text (标签2) │
├── Text (标签很长三) │
├── Text (标签4) │
├── Text (标签5) │─── 第二行 ───│
├── Text (标签六六六) │
├── Text (标签7) │
├── Text (标签8) │─── 第三行 ───│
└── Text (标签九九) │
核心知识点:
wrap: FlexWrap.Wrap:允许 Flex 容器在空间不足时自动折行。这是实现标签云、关键词列表等功能的关键配置
justifyContent: FlexAlign.Start:每行从左到右排列,换行后新行也从左开始。这对于标签列表是最自然的排列方式
padding 而非 width/height:标签使用了内边距而不是固定宽高,这样标签尺寸由内容撑开,更灵活自然
.borderRadius(12) 圆角胶囊:配合 padding 实现胶囊形状的标签,是移动端 UI 的经典样式
运行效果:9 个橙色标签在容器中从左到右排列,当行宽不足时自动换行到下一行,每个标签呈胶囊形状。
自动换行的注意事项:
- FlexWrap 只在子元素总宽度超过容器宽度时生效
- 如果所有子元素宽度之和未超过容器宽度,则所有子元素排在一行
- 不同平台的换行表现可能略有差异,建议在实际设备上测试
- 换行后容器的总高度会根据行数自动增加
5.4 演示四:Column 内嵌 Column(纵向嵌套)
这是最复杂的演示场景,展示了 Flex 内部的纵向细分能力:
Column() {
Text('▶ Column 内嵌 Column(纵向嵌套)')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#EEEEEE')
.width('100%')
.margin({ bottom: 8 })
// 在 Column 内部,也可以再次使用 Column 实现纵向细分
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start }) {
Text('🔹 第一行:说明文字')
.fontSize(12)
.fontColor('#BBBBBB')
.margin({ bottom: 4 })
// 嵌套 Flex 实现混合排列
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceEvenly }) {
ForEach(['子项①', '子项②', '子项③'], (item: string) => {
Text(item)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#9B59B6')
.width(80)
.height(32)
.textAlign(TextAlign.Center)
.borderRadius(6)
})
}
.width('100%')
.margin({ bottom: 4 })
Text('🔸 第二行:更多选项')
.fontSize(12)
.fontColor('#BBBBBB')
.margin({ bottom: 4 })
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceEvenly }) {
ForEach(['选项甲', '选项乙'], (item: string) => {
Text(item)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#1ABC9C')
.width(90)
.height(32)
.textAlign(TextAlign.Center)
.borderRadius(6)
})
}
.width('100%')
}
.width('100%')
.padding(8)
.backgroundColor('#2A2A4A')
.borderRadius(8)
}
.width('100%')
.margin({ bottom: 16 })
布局层次分析:
Column (最外层)
├── Text (区块标题)
└── Flex (方向: Column, 纵向排列)
├── Text (🔹 第一行:说明文字)
├── Flex (方向: Row, SpaceEvenly) — 横向排列 3 个子项
│ ├── Text (子项①)
│ ├── Text (子项②)
│ └── Text (子项③)
├── Text (🔸 第二行:更多选项)
└── Flex (方向: Row, SpaceEvenly) — 横向排列 2 个子项
├── Text (选项甲)
└── Text (选项乙)
核心知识点:
Flex 也可以做纵向容器:通过 Flex({ direction: FlexDirection.Column }),Flex 可以替代 Column 实现纵向排列,同时保留 Flex 的对齐能力
Flex 嵌套 Flex:外层 Flex 是纵向(Column),内层 Flex 是横向(Row),实现了「纵中有横」的复杂布局
SpaceEvenly 均匀分布:子项在容器中均匀分布,首尾与容器边缘的间距等于子项之间的间距
ItemAlign.Start:在外层 Flex 中设置,让所有子元素(文本和内部 Flex)左对齐,而不是默认的拉伸
运行效果:一个纵向列表中包含两行,每行都有说明文字和对应的横向排列的按钮,形成清晰的层次结构。
六、综合实战:社交卡片信息流
理论结合实践,本节将用 Column + Flex 构建一个完整的社交动态流页面。
6.1 页面结构设计
Column() {
Text('💡 综合实战:社交卡片流')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.width('100%')
.margin({ bottom: 12 })
// 卡片 1
// 卡片 2
// 卡片 3
}
.width('85%')
.padding(16)
.backgroundColor('#1E1E3A')
.borderRadius(12)
.margin({ bottom: 20 })
6.2 单张卡片的内部结构
每个卡片都是一个完整的「Flex 横向 + Column 纵向 + Flex 标签」的混合布局:
Column() {
// — 第一行:头像 + 用户信息(Flex 横向) —
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
// 头像
Circle()
.width(44)
.height(44)
.fill('#667eea')
// 信息区(Column 纵向)
Column() {
Text('张同学')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
Text('今天发布了新文章 🎉')
.fontSize(13)
.fontColor('#AAAAAA')
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
}
.width('100%')
.margin({ bottom: 8 })
// — 第二行:标签(Flex 横向换行) —
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(['HarmonyOS', 'ArkTS', '布局教程', 'Column+Flex'], (tag: string) => {
Text(`#${tag}`)
.fontSize(11)
.fontColor('#667eea')
.backgroundColor('#667eea22')
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.borderRadius(8)
.margin({ right: 6, bottom: 4 })
})
}
.width('100%')
}
.width('100%')
.padding(12)
.backgroundColor('#2A2A4A')
.borderRadius(10)
.margin({ bottom: 10 })
布局层次分析:
Column (卡片容器)
├── Flex (Row, alignItems: Center) — 第一行:头像 + 信息
│ ├── Circle (头像占位)
│ └── Column (用户信息)
│ ├── Text (用户名)
│ └── Text (动态内容)
└── Flex (Row, Wrap, Start) — 第二行:标签
├── Text (#HarmonyOS)
├── Text (#ArkTS)
├── Text (#布局教程)
└── Text (#Column+Flex)
6.3 Circle 组件的使用
Circle() 是 ArkUI 内置的形状组件,用于绘制圆形。在本示例中用作头像占位:
Circle()
.width(44)
.height(44)
.fill('#667eea')
Circle 的关键属性:
| width | Length | 圆的宽度(直径) |
| height | Length | 圆的高度(直径) |
| fill | ResourceColor | 填充颜色 |
| stroke | ResourceColor | 描边颜色 |
| strokeWidth | Length | 描边宽度 |
注意:Circle() 的宽高应相等,否则会变成椭圆。
6.4 三张卡片的差异化设计
我们的示例中包含三张卡片,通过不同的颜色方案区分不同的用户:
| 1 | 张同学 | #667eea 紫蓝 | #667eea | HarmonyOS, ArkTS, 布局教程, Column+Flex |
| 2 | 李工 | #2ECC71 翠绿 | #2ECC71 | Bug修复, 性能优化, 代码审查 |
| 3 | 王产品 | #E67E22 橙黄 | #E67E22 | 原型设计, 用户调研, 交互设计 |
这种差异化设计不仅展示了代码的复用性,也展示了 ArkTS 通过数据驱动的能力——实际应用中,三张卡片应该通过 ForEach 遍历数据数组来渲染,而不是硬编码。
七、进阶技巧:用 Column + Flex 实现网格布局
除了基本的横向和纵向排列,Column + Flex 组合还可以模拟网格布局。
7.1 2 列网格
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(['📱 手机', '💻 电脑', '⌚ 手表', '🎧 耳机'], (item: string) => {
Text(item)
.fontSize(14)
.fontColor('#FFFFFF')
.backgroundColor('#764ba2')
.width('48%')
.height(50)
.textAlign(TextAlign.Center)
.borderRadius(8)
})
}
.width('100%')
.margin({ bottom: 8 })
关键技巧:
- 每两个一组,宽度设为 48%(留出 4% 作为间隙)
- SpaceBetween 让两个元素自动分居左右两端
- 多组时重复此模式即可形成多行 2 列网格
7.2 3 列网格
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(['📷 相机', '🎮 游戏', '📖 阅读'], (item: string) => {
Text(item)
.width('31%')
.height(50)
.textAlign(TextAlign.Center)
.borderRadius(8)
})
}
关键技巧:
- 宽度设为 31%(100% 减去 2 个间隙共约 7%,每列约 31%)
- 与 2 列不同的百分比需要手动计算
7.3 4 列网格
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(['🌤 天气', '📅 日历', '📞 电话', '✉️ 短信'], (item: string) => {
Text(item)
.width('23%')
.height(50)
.textAlign(TextAlign.Center)
.borderRadius(8)
})
}
关键技巧:
- 宽度设为 23%(100% 减去 3 个间隙约 8%,每列约 23%)
- 四列正好占满一行
7.4 网格布局总结
| 2 列 | 48% | SpaceBetween 自动两端对齐 |
| 3 列 | 31% | SpaceBetween 自动均分 |
| 4 列 | 23% | SpaceBetween 自动均分 |
通过 Column 纵向排列多行 Flex,我们可以轻松实现类似于 Grid 容器 的网格效果,而且更加灵活——每行的列数可以不同,这是 Grid 容器难以做到的。
八、性能优化与最佳实践
8.1 ForEach 的性能考量
ForEach 是 ArkTS 中遍历渲染列表的关键 API,使用时需要注意:
ForEach(
arr: any[],
itemGenerator: (item: any, index?: number) => void,
keyGenerator?: (item: any, index?: number) => string
)
最佳实践:
ForEach(
items,
(item: MyType) => { /* 渲染逻辑 */ },
(item: MyType) => item.id.toString() // 使用唯一 ID 作为 key
)
避免在回调中执行耗时操作:ForEach 的回调在 build 过程中被调用,应保持轻量
数组不应在 build 方法中创建:将数据数组定义为组件的成员变量或常量,而不是在 build() 中硬编码
8.2 Scroll 与 Column 的组合优化
当使用 Scroll 包裹 Column 时,注意以下配置:
Scroll() {
Column() { /* 内容 */ }
}
.scrollBar(BarState.Off) // 隐藏滚动条(如需要)
.scrollable(ScrollDirection.Vertical) // 仅垂直方向可滚动
优化建议:
- 如果内容确定在一屏内,不要包 Scroll,避免不必要的滚动计算
- 如果内容可能超长,使用 Scroll 是最高效的解决方案
- Scroll 的内部 Column 不需要设置 height,由内容撑开
8.3 深色主题适配
本示例使用深色主题(#0A0A1A 背景),在移动端是常见的设计选择。需要注意:
- 文本颜色使用浅色(#FFFFFF、#AAAAAA 等),保证可读性
- 卡片背景使用比主背景稍亮的颜色(#1E1E3A 等),形成层次
- 按钮等交互元素使用高饱和度颜色,引导用户注意力
如果需要适配浅色主题,可以使用 @Styles 或 @Extend 封装主题样式:
@Styles function cardStyle() {
.width('85%')
.padding(16)
.borderRadius(12)
.margin({ bottom: 20 })
}
8.4 嵌套层级控制
过多的嵌套会影响性能和维护性。建议:
- 通常嵌套不超过 5 层
- 使用 Flex 替代多余的 Column 或 Row
- 复用样式申明减少重复属性设置
本示例中的嵌套层次:
Scroll (1)
└── Column (2)
└── Column — 演示区块 (3)
└── Column — 单个场景 (4)
└── Flex — 子元素行 (5)
└── Text / Circle — 叶节点
这是合理的嵌套深度(5 层),性能无问题。
九、常见问题与解决方案
9.1 Flex 的子元素没有换行
问题:设置了 wrap: FlexWrap.Wrap 但子元素没有换行。
原因:子元素的总宽度可能没有超过容器宽度。
解决方案:
- 确保容器宽度是受限的(设置了明确宽度或百分比)
- 检查子元素的宽度是否足够大
- 临时添加更多子元素测试换行效果
9.2 Column 中的子元素不居中
问题:Column 设置了 alignItems(HorizontalAlign.Center),但子元素没有居中。
原因:Column 的宽度可能被子元素撑满,导致居中无效。
解决方案:
Column() {
// 子元素
}
.width('100%') // 确保 Column 有明确宽度
.alignItems(HorizontalAlign.Center)
9.3 Scroll 不滚动
问题:Scroll 包裹的内容无法滚动。
原因:Scroll 的内容高度没有超过 Scroll 容器的高度。
解决方案:
- 确保 Scroll 设置了固定的高度(如 .height('100%'))
- 确保 Column 内容的总高度超过 Scroll 高度
- 移除外层容器对高度的无限定
9.4 ForEach 的类型错误
问题:ForEach 中报类型错误。
原因:ArkTS 是静态类型语言,ForEach 的回调参数需要显式标注类型。
解决方案:
// ✅ 正确
ForEach(arr, (item: string) => { /* … */ })
// ❌ 错误(缺少类型标注)
ForEach(arr, (item) => { /* … */ })
9.5 FlexAlign 与 ItemAlign 的混淆
问题:Flex 中的对齐效果不符合预期。
原因:将主轴对齐(justifyContent)和交叉轴对齐(alignItems)混淆了。
规则:
| justifyContent | 主轴方向(由 direction 决定) | justify-content |
| alignItems | 交叉轴方向 | align-items |
举例:Flex({ direction: FlexDirection.Row }) 时:
- justifyContent 控制水平方向
- alignItems 控制垂直方向
十、完整源代码
以下是 ColumnFlexDemo.ets 的完整代码(共 493 行),可以直接复制到你的 HarmonyOS 项目中使用:
/**
* Column + Flex 混合方向排列演示页
*
* 📐 布局要点:Column 内嵌 Flex,实现"纵向主轴 + 横向子区"的混合方向排列
*
* 适用场景:
* – 卡片式信息展示(头像横向 + 描述纵向)
* – 表单布局(标签横向 + 内容纵向)
* – Dashboard 仪表盘(混合排版)
*
* 🛠 核心 API:
* – Column({ space }) —— 纵向主轴容器
* – Flex({ direction: FlexDirection.Row, … }) —— 横向弹性容器
* – FlexWrap.Wrap —— 子项自动换行
*/
import { router } from '@kit.ArkUI';
@Entry
@Component
struct ColumnFlexDemo {
build() {
// ============================================================
// 最外层 —— Scroll + Column(可滚动纵向主轴)
// Scroll 保证内容超长时可滚动,Column 提供垂直方向排列
// ============================================================
Scroll() {
Column() {
// ————————————————
// 1) 页面标题
// ————————————————
Text('🧩 Column + Flex 混合排列')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.margin({ top: 48, bottom: 8 })
Text('Column 纵向主轴内嵌 Flex 横向排列 — 一处主轴、多处次轴')
.fontSize(14)
.fontColor('#AAAAAA')
.textAlign(TextAlign.Center)
.width('85%')
.lineHeight(20)
.margin({ bottom: 24 })
// ============================================================
// 2) 布局结构说明卡片
// ============================================================
Column() {
Text('📐 布局结构')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#667eea')
.width('100%')
.margin({ bottom: 8 })
Text('Column(纵向主轴)→ Flex(横向弹性行)→ 子元素')
.fontSize(13)
.fontColor('#CCCCCC')
.width('100%')
Text('Column 是整个页面的纵向容器,保证所有块从上到下排列。')
.fontSize(12)
.fontColor('#888888')
.width('100%')
.lineHeight(18)
.margin({ top: 8 })
Text('在每个区块内部,使用 Flex(FlexDirection.Row) 实现水平排列,支持换行、居中等弹性布局能力。')
.fontSize(12)
.fontColor('#888888')
.width('100%')
.lineHeight(18)
.margin({ top: 4 })
}
.width('85%')
.padding(16)
.backgroundColor('#1A1A2E')
.borderRadius(12)
.margin({ bottom: 20 })
// ============================================================
// 3) 演示区域 —— Column 作为主轴容器
// 每个区块都是一个 Flex 横向行,实现"纵向方向中的横向排列"
// ============================================================
Column() {
// —- 第 1 组:Flex 横向排列 + 居中 —-
Column() {
Text('▶ 横向 Flex(居中)')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#EEEEEE')
.width('100%')
.margin({ bottom: 8 })
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center }) {
ForEach(['🏠 首页', '📁 项目', '📄 文档', '⚙️ 设置'], (item: string, index: number) => {
Text(item)
.fontSize(13)
.fontColor('#FFFFFF')
.backgroundColor(index % 2 === 0 ? '#667eea' : '#764ba2')
.width(70)
.height(36)
.textAlign(TextAlign.Center)
.borderRadius(8)
.margin(4)
})
}
.width('100%')
.padding(8)
.backgroundColor('#2A2A4A')
.borderRadius(8)
}
.width('100%')
.margin({ bottom: 16 })
// —- 第 2 组:Flex 横向排列 + 两端对齐 —-
Column() {
Text('▶ 横向 Flex(两端对齐)')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#EEEEEE')
.width('100%')
.margin({ bottom: 8 })
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(['❄️ 功能 A', '🔥 功能 B', '💧 功能 C'], (item: string) => {
Text(item)
.fontSize(13)
.fontColor('#FFFFFF')
.backgroundColor('#2ECC71')
.width(90)
.height(40)
.textAlign(TextAlign.Center)
.borderRadius(8)
})
}
.width('100%')
.padding(8)
.backgroundColor('#2A2A4A')
.borderRadius(8)
}
.width('100%')
.margin({ bottom: 16 })
// —- 第 3 组:Flex 换行排列(Wrap) —-
Column() {
Text('▶ 横向 Flex(自动换行)')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#EEEEEE')
.width('100%')
.margin({ bottom: 8 })
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(
['标签1', '标签2', '标签很长三', '标签4', '标签5', '标签六六六', '标签7', '标签8', '标签九九'],
(item: string) => {
Text(item)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#E67E22')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(12)
.margin(4)
}
)
}
.width('100%')
.padding(8)
.backgroundColor('#2A2A4A')
.borderRadius(8)
}
.width('100%')
.margin({ bottom: 16 })
// —- 第 4 组:Flex 列排列(Column 内再次纵向) —-
Column() {
Text('▶ Column 内嵌 Column(纵向嵌套)')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#EEEEEE')
.width('100%')
.margin({ bottom: 8 })
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start }) {
Text('🔹 第一行:说明文字')
.fontSize(12)
.fontColor('#BBBBBB')
.margin({ bottom: 4 })
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceEvenly }) {
ForEach(['子项①', '子项②', '子项③'], (item: string) => {
Text(item)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#9B59B6')
.width(80)
.height(32)
.textAlign(TextAlign.Center)
.borderRadius(6)
})
}
.width('100%')
.margin({ bottom: 4 })
Text('🔸 第二行:更多选项')
.fontSize(12)
.fontColor('#BBBBBB')
.margin({ bottom: 4 })
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceEvenly }) {
ForEach(['选项甲', '选项乙'], (item: string) => {
Text(item)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#1ABC9C')
.width(90)
.height(32)
.textAlign(TextAlign.Center)
.borderRadius(6)
})
}
.width('100%')
}
.width('100%')
.padding(8)
.backgroundColor('#2A2A4A')
.borderRadius(8)
}
.width('100%')
.margin({ bottom: 16 })
}
.width('85%')
.padding(16)
.backgroundColor('#1E1E3A')
.borderRadius(12)
.margin({ bottom: 20 })
// ============================================================
// 4) 综合示例:社交卡片信息流
// 体现 "Column 主轴 + Flex 横向" 的实战场景
// ============================================================
Column() {
Text('💡 综合实战:社交卡片流')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.width('100%')
.margin({ bottom: 12 })
// —- 卡片 1 —-
Column() {
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
Circle()
.width(44)
.height(44)
.fill('#667eea')
Column() {
Text('张同学')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
Text('今天发布了新文章 🎉')
.fontSize(13)
.fontColor('#AAAAAA')
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
}
.width('100%')
.margin({ bottom: 8 })
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(['HarmonyOS', 'ArkTS', '布局教程', 'Column+Flex'], (tag: string) => {
Text(`#${tag}`)
.fontSize(11)
.fontColor('#667eea')
.backgroundColor('#667eea22')
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.borderRadius(8)
.margin({ right: 6, bottom: 4 })
})
}
.width('100%')
}
.width('100%')
.padding(12)
.backgroundColor('#2A2A4A')
.borderRadius(10)
.margin({ bottom: 10 })
// —- 卡片 2 —-
Column() {
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
Circle()
.width(44)
.height(44)
.fill('#2ECC71')
Column() {
Text('李工')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
Text('修复了 3 个 Bug ✅')
.fontSize(13)
.fontColor('#AAAAAA')
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
}
.width('100%')
.margin({ bottom: 8 })
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(['Bug修复', '性能优化', '代码审查'], (tag: string) => {
Text(`#${tag}`)
.fontSize(11)
.fontColor('#2ECC71')
.backgroundColor('#2ECC7122')
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.borderRadius(8)
.margin({ right: 6, bottom: 4 })
})
}
.width('100%')
}
.width('100%')
.padding(12)
.backgroundColor('#2A2A4A')
.borderRadius(10)
.margin({ bottom: 10 })
// —- 卡片 3 —-
Column() {
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
Circle()
.width(44)
.height(44)
.fill('#E67E22')
Column() {
Text('王产品')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
Text('新功能原型设计完成 📝')
.fontSize(13)
.fontColor('#AAAAAA')
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
}
.width('100%')
.margin({ bottom: 8 })
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(['原型设计', '用户调研', '交互设计'], (tag: string) => {
Text(`#${tag}`)
.fontSize(11)
.fontColor('#E67E22')
.backgroundColor('#E67E2222')
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.borderRadius(8)
.margin({ right: 6, bottom: 4 })
})
}
.width('100%')
}
.width('100%')
.padding(12)
.backgroundColor('#2A2A4A')
.borderRadius(10)
.margin({ bottom: 16 })
}
.width('85%')
.padding(16)
.backgroundColor('#1E1E3A')
.borderRadius(12)
.margin({ bottom: 20 })
// ============================================================
// 5) Column 内嵌 Flex 的网格布局
// 利用多行 Flex 模拟 Grid 网格效果
// ============================================================
Column() {
Text('📊 Column 作为网格容器(Flex 实现 Grid 效果)')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.width('100%')
.margin({ bottom: 12 })
// —- 第 1 行:2 列 —-
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(['📱 手机', '💻 电脑', '⌚ 手表', '🎧 耳机'], (item: string) => {
Text(item)
.fontSize(14)
.fontColor('#FFFFFF')
.backgroundColor('#764ba2')
.width('48%')
.height(50)
.textAlign(TextAlign.Center)
.borderRadius(8)
})
}
.width('100%')
.margin({ bottom: 8 })
// —- 第 2 行:3 列 —-
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(['📷 相机', '🎮 游戏', '📖 阅读'], (item: string) => {
Text(item)
.fontSize(14)
.fontColor('#FFFFFF')
.backgroundColor('#E74C3C')
.width('31%')
.height(50)
.textAlign(TextAlign.Center)
.borderRadius(8)
})
}
.width('100%')
.margin({ bottom: 8 })
// —- 第 3 行:4 列 —-
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(['🌤 天气', '📅 日历', '📞 电话', '✉️ 短信'], (item: string) => {
Text(item)
.fontSize(13)
.fontColor('#FFFFFF')
.backgroundColor('#3498DB')
.width('23%')
.height(50)
.textAlign(TextAlign.Center)
.borderRadius(8)
})
}
.width('100%')
}
.width('85%')
.padding(16)
.backgroundColor('#1E1E3A')
.borderRadius(12)
.margin({ bottom: 24 })
// ============================================================
// 6) 返回按钮
// ============================================================
Button() {
Row() {
Text('⬅️')
.fontSize(20)
.margin({ right: 8 })
Text('返回首页')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
}
.alignItems(VerticalAlign.Center)
}
.width('60%')
.height(48)
.backgroundColor('#667eea')
.borderRadius(24)
.onClick(() => {
router.back();
})
.margin({ bottom: 40 })
}
.width('100%')
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height('100%')
.backgroundColor('#0A0A1A')
}
}
十一、总结与展望
11.1 核心要点回顾
通过本文的学习,我们掌握了:
11.2 适用场景建议
| 普通信息列表 | 纯 Column |
| 导航菜单 | Column 包裹 Flex(Center) |
| 功能入口 | Column 包裹 Flex(SpaceBetween) |
| 标签云 / 关键词列表 | Column 包裹 Flex(Wrap) |
| 社交动态流 | Column 包裹多个卡片(每张卡片内 Flex 混合排列) |
| 仪表盘网格 | Column 包裹多行 Flex(SpaceBetween) |
11.3 下一步学习方向
- Row 组件:Column 的横向版,用于横向主轴布局
- Stack 组件:层叠布局,适合悬浮按钮、覆盖层等场景
- Grid 和 GridItem:ArkTS 的网格容器,适合相册、商品展示等规则网格
- @State 和状态管理:让布局响应用户交互和数据变化
- @Builder 装饰器:封装可复用的布局片段
11.4 写在最后
HarmonyOS NEXT 的 ArkTS 声明式 UI 框架为开发者提供了强大而灵活的布局能力。Column 和 Flex 作为最基础的布局组件,它们的组合使用几乎可以满足所有日常开发需求。掌握「Column 弹性布局 + Flex 混合方向排列」的模式,是每位 HarmonyOS 开发者从入门到进阶的必经之路。
本文中的代码示例已经过 HarmonyOS NEXT 6.1.1(API 24)编译验证,确保零错误运行。建议读者在实际项目中使用时,根据自己的 UI 设计灵活调整颜色、间距和尺寸参数,创造出更丰富的视觉效果。
致谢:感谢阅读本文。如果你觉得这篇文章有帮助,欢迎点赞收藏,也欢迎在评论区分享你的布局经验和心得。



