ArkUI 中 Flex 和 Grid 布局的选择与实践
最近在做一个鸿蒙应用的时候,遇到了一个布局问题:页面需要适配不同屏幕尺寸,有些地方用 Flex 布局,有些地方用 Grid 布局,但总是感觉用得不顺手。有时候用 Flex 实现的效果,用 Grid 会更简单;有时候用 Grid 实现的效果,用 Flex 反而更灵活。后来仔细研究了一下,才发现 Flex 和 Grid 虽然都是布局容器,但它们的适用场景完全不同。Flex 适合一维布局,比如一行或一列

大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
前言
最近在做一个鸿蒙应用的时候,遇到了一个布局问题:页面需要适配不同屏幕尺寸,有些地方用 Flex 布局,有些地方用 Grid 布局,但总是感觉用得不顺手。有时候用 Flex 实现的效果,用 Grid 会更简单;有时候用 Grid 实现的效果,用 Flex 反而更灵活。
后来仔细研究了一下,才发现 Flex 和 Grid 虽然都是布局容器,但它们的适用场景完全不同。Flex 适合一维布局,比如一行或一列的内容排列;Grid 适合二维布局,比如表格、网格等。而且在实际项目中,通常是 Grid 定义整体骨架,Flex 处理局部细节。
今天我们就来聊聊 ArkUI 中 Flex 和 Grid 的区别,以及如何根据不同的布局需求选择最合适的容器。
问题背景
在 ArkUI 开发中,我们经常需要实现响应式布局,让应用能够适配不同尺寸的屏幕。ArkUI 提供了两个主要的布局容器:Flex(通过 Column 和 Row 实现)和 Grid。
很多开发者刚开始接触这两个容器时,可能会感到困惑:
- 什么时候用 Flex,什么时候用 Grid?
- 它们有什么区别?
- 如何根据实际需求选择?
这些问题如果不搞清楚,可能会导致布局代码混乱,或者实现效果不理想。让我们先了解一下这两个容器的特点和区别。
Flex 布局:一维布局的利器
Flex 布局是 ArkUI 中实现一维布局的主要方式,通过 Column(列)和 Row(行)来实现。
Flex 的核心特点
Flex 布局的核心思想是:在一个方向上(水平或垂直)排列子组件,并控制它们之间的对齐、间距和大小分配。
一维布局:Flex 只能在一个方向上排列元素,要么是水平方向(Row),要么是垂直方向(Column)。这是它和 Grid 最大的区别。
灵活的对齐方式:Flex 提供了丰富的对齐选项,比如:
MainAxisAlignment:主轴对齐(水平方向的对齐)CrossAxisAlignment:交叉轴对齐(垂直方向的对齐)SpaceBetween、SpaceAround、SpaceEvenly:空间分配方式
响应速度快:由于 Flex 布局相对简单,计算量小,所以响应速度比较快,适合频繁更新的场景。
Flex 的适用场景
Flex 布局最适合处理微观布局,也就是局部的小范围布局:
导航栏和工具栏:导航栏通常是一行按钮或图标,用 Row 就能很好地处理:
@Entry
@Component
struct NavigationBar {
build() {
Row() {
Image($r('app.media.back'))
.width(24)
.height(24)
.onClick(() => {
// 返回
})
Text('标题')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
Image($r('app.media.more'))
.width(24)
.height(24)
.onClick(() => {
// 更多操作
})
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
}
}
列表项内部:列表项通常包含图片、标题、描述等信息,用 Column 或 Row 组合就能实现:
@Component
struct ListItem {
build() {
Row() {
Image($r('app.media.avatar'))
.width(48)
.height(48)
.borderRadius(24)
Column() {
Text('用户名')
.fontSize(16)
.fontWeight(FontWeight.Medium)
Text('这是用户描述信息')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
Text('关注')
.fontSize(14)
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor('#007AFF')
.fontColor(Color.White)
.borderRadius(4)
}
.width('100%')
.padding(16)
.alignItems(VerticalAlign.Center)
}
}
按钮组:多个按钮横向或纵向排列,用 Flex 很容易实现:
@Component
struct ButtonGroup {
build() {
Row() {
Button('取消')
.type(ButtonType.Normal)
.layoutWeight(1)
.onClick(() => {
// 取消操作
})
Button('确认')
.type(ButtonType.Normal)
.layoutWeight(1)
.margin({ left: 12 })
.onClick(() => {
// 确认操作
})
}
.width('100%')
.padding(16)
.justifyContent(FlexAlign.SpaceBetween)
}
}
表单行:表单中的每一行通常包含标签和输入框,用 Row 就能很好地处理:
@Component
struct FormRow {
private label: string = '用户名'
private placeholder: string = '请输入用户名'
build() {
Row() {
Text(this.label)
.fontSize(16)
.width(80)
TextInput({ placeholder: this.placeholder })
.layoutWeight(1)
.margin({ left: 16 })
}
.width('100%')
.height(48)
.padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Center)
}
}
Flex 的优势
Flex 布局的优势主要体现在:
- 简单直观:一维布局的概念很容易理解,代码也相对简单
- 性能好:计算量小,响应速度快
- 灵活性强:对齐、间距、大小分配等都可以精确控制
- 适合局部布局:处理小范围的布局问题非常方便
Grid 布局:二维布局的强大工具
Grid 布局是 ArkUI 中实现二维布局的主要方式,可以同时处理行和列的排列。
Grid 的核心特点
Grid 布局的核心思想是:将容器划分为行和列的网格,子组件按照网格规则排列。
二维布局:Grid 可以同时在水平和垂直两个方向上排列元素,这是它和 Flex 最大的区别。
列模板定义:Grid 通过 columnsTemplate 定义列的宽度规则,比如 '1fr 1fr 1fr' 表示三列等宽,'repeat(3, 1fr)' 表示重复三列等宽。
响应式断点:Grid 可以根据不同屏幕尺寸定义不同的列模板,实现响应式布局:
Grid() {
// 子组件
}
.columnsTemplate('1fr 1fr') // 小屏幕:两列
.columnsTemplate('repeat(3, 1fr)') // 大屏幕:三列
网格对齐:Grid 提供了 rowsGap 和 columnsGap 来控制网格间距,还支持 layoutDirection 控制排列方向。
Grid 的适用场景
Grid 布局最适合处理宏观布局,也就是页面整体的结构:
页面整体结构:页面的整体布局,比如侧边栏+主内容区的结构:
@Entry
@Component
struct MainLayout {
build() {
Grid() {
// 侧边栏
Column() {
Text('侧边栏')
.fontSize(16)
// 侧边栏内容
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
// 主内容区
Column() {
Text('主内容区')
.fontSize(16)
// 主内容
}
.width('100%')
.height('100%')
.backgroundColor(Color.White)
}
.columnsTemplate('200px 1fr') // 侧边栏固定宽度,主内容区自适应
.rowsTemplate('1fr')
.width('100%')
.height('100%')
}
}
产品展示列表:商品列表、图片画廊等需要网格排列的场景:
@Component
struct ProductGrid {
private products: Product[] = []
build() {
Grid() {
ForEach(this.products, (product: Product) => {
GridItem() {
Column() {
Image(product.image)
.width('100%')
.aspectRatio(1)
.borderRadius(8)
Text(product.name)
.fontSize(14)
.margin({ top: 8 })
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(`¥${product.price}`)
.fontSize(16)
.fontColor('#FF6B35')
.fontWeight(FontWeight.Bold)
.margin({ top: 4 })
}
.width('100%')
.padding(8)
}
}, (product: Product) => product.id.toString())
}
.columnsTemplate('1fr 1fr') // 两列布局
.rowsGap(12)
.columnsGap(12)
.width('100%')
.padding(16)
}
}
仪表盘布局:数据展示的仪表盘,通常需要多个卡片按网格排列:
@Component
struct Dashboard {
build() {
Grid() {
// 统计卡片1
GridItem() {
StatCard({ title: '总用户', value: '1000' })
}
// 统计卡片2
GridItem() {
StatCard({ title: '活跃用户', value: '800' })
}
// 统计卡片3
GridItem() {
StatCard({ title: '新增用户', value: '200' })
}
// 统计卡片4
GridItem() {
StatCard({ title: '留存率', value: '80%' })
}
// 图表区域(跨两列)
GridItem() {
ChartCard()
}
.columnStart(0)
.columnEnd(1)
}
.columnsTemplate('1fr 1fr') // 两列布局
.rowsTemplate('repeat(3, 1fr)') // 三行布局
.rowsGap(16)
.columnsGap(16)
.width('100%')
.padding(16)
}
}
Grid 的优势
Grid 布局的优势主要体现在:
- 二维布局:可以同时处理行和列的排列,适合复杂的网格结构
- 响应式设计:通过列模板可以轻松实现不同屏幕尺寸下的布局重排
- 多端适配:通过断点设置,可以适配手机、平板、PC 等不同设备
- 适合整体布局:定义页面整体结构非常方便
如何选择合适的布局容器
在实际开发中,如何选择 Flex 还是 Grid 呢?我们可以从以下几个角度来考虑:
维度判断:一维还是二维
最直接的判断方法就是看布局的维度:
一维布局用 Flex:如果只需要在一个方向上排列元素(水平或垂直),就用 Flex。比如导航栏、工具栏、列表项等。
二维布局用 Grid:如果需要在行和列两个方向上排列元素,就用 Grid。比如商品列表、仪表盘、页面整体结构等。
范围判断:局部还是整体
另一个判断方法是看布局的范围:
局部布局用 Flex:处理小范围的布局问题,比如组件内部的对齐、间距等,用 Flex 更合适。
整体布局用 Grid:定义页面整体的结构,比如侧边栏+主内容区、多列布局等,用 Grid 更合适。
复杂度判断:简单还是复杂
还可以从复杂度来判断:
简单布局用 Flex:如果布局逻辑简单,只是简单的排列和对齐,用 Flex 就够了。
复杂布局用 Grid:如果布局逻辑复杂,需要网格结构、响应式断点等,用 Grid 更合适。
最佳实践:组合使用
在实际项目中,Flex 和 Grid 通常是组合使用的,而不是单独使用。
整体用 Grid,局部用 Flex
最佳实践是:用 Grid 定义页面的整体响应式骨架,然后用 Flex 在 Grid 的每个单元格内部对内容进行精确的对齐和排布。
示例:商品列表页面
@Entry
@Component
struct ProductListPage {
private products: Product[] = []
build() {
Column() {
// 顶部导航栏(Flex)
Row() {
Image($r('app.media.back'))
.width(24)
.height(24)
Text('商品列表')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
Image($r('app.media.search'))
.width(24)
.height(24)
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
// 商品网格(Grid)
Grid() {
ForEach(this.products, (product: Product) => {
GridItem() {
// 商品卡片内部(Flex)
Column() {
// 图片区域
Image(product.image)
.width('100%')
.aspectRatio(1)
.borderRadius(8)
// 信息区域(Flex)
Column() {
Text(product.name)
.fontSize(14)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
// 价格和按钮(Flex)
Row() {
Text(`¥${product.price}`)
.fontSize(16)
.fontColor('#FF6B35')
.fontWeight(FontWeight.Bold)
Button('加入购物车')
.type(ButtonType.Normal)
.fontSize(12)
.margin({ left: 8 })
}
.width('100%')
.margin({ top: 8 })
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
}
.width('100%')
.padding(8)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({
radius: 4,
color: '#00000010',
offsetX: 0,
offsetY: 2
})
}
}, (product: Product) => product.id.toString())
}
.columnsTemplate('1fr 1fr') // Grid 定义整体结构
.rowsGap(12)
.columnsGap(12)
.width('100%')
.layoutWeight(1)
.padding(16)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
这个例子展示了:
- 导航栏用 Row(Flex):处理一行按钮的对齐
- 商品列表用 Grid:定义整体的网格结构
- 商品卡片内部用 Column(Flex):处理卡片内部内容的排列
- 价格和按钮用 Row(Flex):处理同一行的对齐
响应式布局的实现
在实际项目中,我们通常需要实现响应式布局,让应用能够适配不同屏幕尺寸。这时候 Grid 的优势就体现出来了:
@Component
struct ResponsiveLayout {
@State private screenWidth: number = 0
aboutToAppear() {
// 获取屏幕宽度
this.screenWidth = getContext(this).getResourceManager().getDeviceCapability().screenWidth
}
build() {
Grid() {
// 内容
}
.columnsTemplate(this.getColumnsTemplate())
.width('100%')
}
private getColumnsTemplate(): string {
// 根据屏幕宽度返回不同的列模板
if (this.screenWidth < 600) {
return '1fr' // 小屏幕:单列
} else if (this.screenWidth < 1024) {
return '1fr 1fr' // 中等屏幕:两列
} else {
return 'repeat(3, 1fr)' // 大屏幕:三列
}
}
}
实际应用场景
让我们看看几个实际应用场景,了解如何在实际项目中应用这些布局方案。
场景一:电商应用的商品列表
在电商应用中,商品列表是一个典型的场景:
需求分析:
- 需要网格布局展示商品
- 不同屏幕尺寸需要不同的列数
- 每个商品卡片内部需要精确对齐
实现方案:
- 整体用 Grid:定义商品网格结构
- 卡片内部用 Flex:处理图片、标题、价格等的排列
@Component
struct ProductGrid {
private products: Product[] = []
build() {
Grid() {
ForEach(this.products, (product: Product) => {
GridItem() {
// Grid 定义整体结构
this.buildProductCard(product)
}
}, (product: Product) => product.id.toString())
}
.columnsTemplate(this.getColumnsTemplate())
.rowsGap(16)
.columnsGap(16)
.width('100%')
.padding(16)
}
@Builder
buildProductCard(product: Product) {
// Flex 处理卡片内部布局
Column() {
Image(product.image)
.width('100%')
.aspectRatio(1)
.borderRadius(8)
Column() {
Text(product.name)
.fontSize(14)
.maxLines(2)
Row() {
Text(`¥${product.price}`)
.fontSize(16)
.fontColor('#FF6B35')
Text(`已售${product.sold}`)
.fontSize(12)
.fontColor('#999999')
.margin({ left: 8 })
}
.width('100%')
.margin({ top: 8 })
.justifyContent(FlexAlign.SpaceBetween)
}
.width('100%')
.padding(8)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
}
private getColumnsTemplate(): string {
// 响应式列模板
const screenWidth = getContext(this).getResourceManager().getDeviceCapability().screenWidth
if (screenWidth < 600) {
return '1fr 1fr' // 手机:两列
} else {
return 'repeat(3, 1fr)' // 平板:三列
}
}
}
场景二:新闻应用的列表页面
在新闻应用中,列表页面也是一个常见场景:
需求分析:
- 需要列表布局展示新闻
- 每个列表项包含图片、标题、摘要、时间等
- 需要适配不同屏幕尺寸
实现方案:
- 整体用 Column(Flex):列表是垂直排列的
- 列表项内部用 Row(Flex):处理图片和文字的横向排列
@Component
struct NewsList {
private newsList: News[] = []
build() {
Column() {
ForEach(this.newsList, (news: News) => {
this.buildNewsItem(news)
}, (news: News) => news.id.toString())
}
.width('100%')
.padding(16)
}
@Builder
buildNewsItem(news: News) {
Row() {
Image(news.image)
.width(120)
.height(80)
.borderRadius(8)
.objectFit(ImageFit.Cover)
Column() {
Text(news.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(news.summary)
.fontSize(14)
.fontColor('#666666')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ top: 8 })
Row() {
Text(news.category)
.fontSize(12)
.fontColor('#007AFF')
Text(news.time)
.fontSize(12)
.fontColor('#999999')
.margin({ left: 12 })
}
.width('100%')
.margin({ top: 8 })
.justifyContent(FlexAlign.SpaceBetween)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
}
.width('100%')
.padding({ top: 12, bottom: 12 })
.borderRadius(8)
.onClick(() => {
// 跳转到详情页
})
}
}
场景三:设置页面
设置页面通常包含多个设置项,每个设置项是一行:
需求分析:
- 需要列表布局展示设置项
- 每个设置项包含图标、标题、描述、开关等
- 需要分组显示
实现方案:
- 整体用 Column(Flex):设置项是垂直排列的
- 每个设置项用 Row(Flex):处理图标、文字、开关的横向排列
@Component
struct SettingsPage {
build() {
Column() {
// 设置组1
this.buildSettingGroup('账户设置', [
{ icon: $r('app.media.profile'), title: '个人资料', hasArrow: true },
{ icon: $r('app.media.security'), title: '安全设置', hasArrow: true },
{ icon: $r('app.media.privacy'), title: '隐私设置', hasArrow: true }
])
// 设置组2
this.buildSettingGroup('通知设置', [
{ icon: $r('app.media.notification'), title: '推送通知', hasSwitch: true },
{ icon: $r('app.media.sound'), title: '声音', hasSwitch: true },
{ icon: $r('app.media.vibration'), title: '震动', hasSwitch: true }
])
}
.width('100%')
.backgroundColor('#F5F5F5')
}
@Builder
buildSettingGroup(title: string, items: SettingItem[]) {
Column() {
Text(title)
.fontSize(14)
.fontColor('#999999')
.margin({ left: 16, top: 16, bottom: 8 })
Column() {
ForEach(items, (item: SettingItem) => {
this.buildSettingItem(item)
}, (item: SettingItem) => item.title)
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(8)
.margin({ left: 16, right: 16, bottom: 16 })
}
.width('100%')
}
@Builder
buildSettingItem(item: SettingItem) {
Row() {
Image(item.icon)
.width(24)
.height(24)
Text(item.title)
.fontSize(16)
.layoutWeight(1)
.margin({ left: 12 })
if (item.hasSwitch) {
Toggle({ type: ToggleType.Switch })
.selectedColor('#007AFF')
}
if (item.hasArrow) {
Image($r('app.media.arrow'))
.width(16)
.height(16)
.margin({ left: 8 })
}
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Center)
.onClick(() => {
// 处理点击事件
})
}
}
常见误区和注意事项
在实际开发中,有一些常见的误区和注意事项:
误区一:过度使用 Grid
有些开发者可能会过度使用 Grid,即使是一维布局也用 Grid。这样会导致代码复杂,性能也不好。
错误示例:
// 错误:用 Grid 实现一行按钮
Grid() {
Button('按钮1')
Button('按钮2')
Button('按钮3')
}
.columnsTemplate('1fr 1fr 1fr')
正确做法:
// 正确:用 Row 实现一行按钮
Row() {
Button('按钮1')
.layoutWeight(1)
Button('按钮2')
.layoutWeight(1)
Button('按钮3')
.layoutWeight(1)
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
误区二:Flex 嵌套过深
有些开发者可能会用 Flex 嵌套来实现复杂的布局,导致代码可读性差,性能也不好。
错误示例:
// 错误:Flex 嵌套过深
Column() {
Row() {
Column() {
Row() {
// 内容
}
}
}
}
正确做法:
// 正确:用 Grid 处理复杂布局
Grid() {
// 内容
}
.columnsTemplate('1fr 1fr')
注意事项
- 性能考虑:Grid 的计算量比 Flex 大,如果不需要二维布局,尽量用 Flex
- 响应式设计:Grid 更适合实现响应式布局,可以通过列模板适配不同屏幕
- 组合使用:实际项目中通常是 Grid 和 Flex 组合使用,而不是单独使用
- 代码可读性:选择合适的布局容器可以让代码更清晰,更容易维护
总结
Flex 和 Grid 是 ArkUI 中两个重要的布局容器,它们各有特点,适用场景也不同:
Flex 布局:
- 适合一维布局(水平或垂直)
- 适合局部布局(组件内部)
- 性能好,响应速度快
- 适合处理对齐、间距、大小分配等细节
Grid 布局:
- 适合二维布局(行和列)
- 适合整体布局(页面结构)
- 支持响应式设计
- 适合处理网格结构、多端适配等
最佳实践:
- 用 Grid 定义页面的整体响应式骨架
- 用 Flex 在 Grid 的每个单元格内部对内容进行精确的对齐和排布
- 根据实际需求选择合适的容器,不要过度使用
更多推荐

所有评论(0)