【共创季稿事节】鸿蒙原生 ArkTS 布局之道:Grid 自适应列数 — autoFill / autoFit 的妙用
鸿蒙原生 ArkTS 布局之道:Grid 自适应列数 — autoFill / autoFit 的妙用



一、引言:为什么需要自适应列数布局
在移动端与多端统一的应用开发中,不同屏幕尺寸、不同设备形态(折叠屏、平板、手机横竖屏)的适配问题,一直是开发者面临的核心痛点之一。传统做法往往是:
- 写死列数,用媒体查询(Media Query)分段切换;
- 使用 Flex 换行布局,靠
wrap属性折行; - 引入第三方瀑布流或网格库,徒增包体积和维护成本。
这些方案要么不够灵活(列数突变不够平滑),要么性能较差(Flex 多层嵌套),要么需要额外的计算逻辑。
HarmonyOS NEXT 提供的 ArkTS Grid 组件,内置了 repeat(auto-fill / auto-fit) 语法,让「根据容器宽度自动决定列数」这件事变得极其优雅——一行代码,零计算,零媒体查询。
本文将以一个完整的交互式示例为线索,从原理到实战,从差异到选型,全方位剖析这一布局利器。
二、Grid 组件的核心布局模型
2.1 什么是 Grid
Grid 是 ArkUI 提供的二维栅格布局容器,由行(Row)和列(Column)组成网格体系。每个子项通过 GridItem 包裹,放置在网格单元中。
Grid() {
GridItem() { /* 子内容 */ }
GridItem() { /* 子内容 */ }
GridItem() { /* 子内容 */ }
}
.columnsTemplate('...') // 列模板
.rowsTemplate('...') // 行模板(可选)
columnsTemplate 和 rowsTemplate 接受一个轨道描述字符串,遵循 CSS Grid 的轨道语法。
2.2 columnsTemplate 语法速览
columnsTemplate('1fr 1fr 1fr') // 三等分
columnsTemplate('100vp 1fr 2fr') // 固定 + 弹性混合
columnsTemplate('repeat(3, 1fr)') // repeat(count, expr)
columnsTemplate('repeat(auto-fill, minmax(80vp, 1fr))') // 自适应列数
columnsTemplate('repeat(auto-fit, minmax(80vp, 1fr))') // 自适应列数(折叠空列)
其中 repeat(auto-fill/fit, minmax(min, max)) 是本次讨论的核心。
2.3 minmax 的桥梁作用
minmax(min, max) 定义了每列的最小宽度和最大宽度。以 minmax(80vp, 1fr) 为例:
- 80vp:列宽的下限,保证卡片不会太窄而影响可读性;
- 1fr:列宽的上限,允许剩余空间在列之间均匀分配。
这一组合使得网格既能容纳最少列数(当容器很窄时),又能弹性拉伸(当容器很宽时)。
三、auto-fill 与 auto-fit 的深层差异
这是最容易混淆也最值得深入理解的部分。两者在绝大多数场景下表现相似,但在容器宽度超出内容所需时,行为截然不同。
3.1 auto-fill:宁可留空,也要整齐
行为:尽可能多地创建列轨道,即使内容不足以填满所有列,空列也会占据其轨道空间。
容器宽度 = 400vp,卡片最小宽度 = 80vp
→ 理论最大列数 = 400 / 80 = 5 列
如果有 3 张卡片:
auto-fill 依然创建 5 列,后 2 列是空列(空心轨道)
适用场景:
- 底部导航栏的网格图标排列;
- 仪表盘 / 监控面板,需要严格对齐视觉网格的场景;
- 表格类数据展示,要求每行高度统一的场景。
3.2 auto-fit:内容优先,尽量铺满
行为:同样尽可能多地创建列轨道,但当内容不足时,会折叠(collapse)空列轨道,让内容列平分剩余空间。
容器宽度 = 400vp,卡片最小宽度 = 80vp
→ 理论最大列数 = 5 列
如果有 3 张卡片:
auto-fit 最初也创建 5 列,但发现后 2 列没有内容,
于是折叠这 2 列,3 张卡片平分 400vp 宽度
适用场景:
- 商品陈列、照片墙、卡片流;
- 搜索结果页(结果数量动态变化);
- 任何「内容数量不确定,但希望尽量铺满」的场景。
3.3 视觉对比速查表
| 维度 | auto-fill | auto-fit |
|---|---|---|
| 空列轨道 | 保留 | 折叠 |
| 内容列宽度 | 维持 minmax 弹性 | 更宽(因空列被折叠) |
| 视觉对齐 | 严格对齐 | 内容优先 |
| 典型场景 | 仪表盘、表格 | 商品墙、卡片流 |
一条经验法则:如果你希望空位置也占位以维持视觉节奏,用
auto-fill;如果你希望内容尽量放大填满空间,用auto-fit。
四、完整示例解析(API 24)
以下是我们构建的交互式示例的核心代码。该代码已在 HarmonyOS NEXT API 24(SDK 7.0+) 环境下编译通过。
4.1 项目结构
entry/src/main/ets/pages/GridAutoFillExample.ets
包含:
CardItem数据接口CardItemView卡片子组件(正方形、彩色、居中文字)AutoFillGrid/AutoFitGrid两种模式的 Grid 组件GridAutoFillExample主页面(@Entry 入口)
4.2 核心代码片段
数据模型
interface CardItem {
id: number;
title: string;
color: ResourceColor;
}
function generateCards(count: number): CardItem[] {
const colors: ResourceColor[] = [
'#FF6B81', '#FDCB6E', '#00B894', '#0984E3',
'#6C5CE7', '#FD79A8', '#00CEC9', '#E17055'
];
const cards: CardItem[] = [];
for (let i = 0; i < count; i++) {
cards.push({
id: i + 1,
title: `卡片 ${i + 1}`,
color: colors[i % colors.length]
});
}
return cards;
}
auto-fill Grid 组件
@Component
struct AutoFillGrid {
cards: CardItem[] = [];
build() {
Grid() {
ForEach(this.cards, (item: CardItem) => {
GridItem() {
CardItemView({ item: item })
}
}, (item: CardItem) => item.id.toString())
}
.columnsTemplate('repeat(auto-fill, minmax(80vp, 1fr))')
.rowsGap(10)
.columnsGap(10)
.width('100%')
.padding(10)
.backgroundColor('#F0F0F0')
.borderRadius(16)
}
}
auto-fit Grid 组件
@Component
struct AutoFitGrid {
cards: CardItem[] = [];
build() {
Grid() {
ForEach(this.cards, (item: CardItem) => {
GridItem() {
CardItemView({ item: item })
}
}, (item: CardItem) => item.id.toString())
}
.columnsTemplate('repeat(auto-fit, minmax(80vp, 1fr))')
.rowsGap(10)
.columnsGap(10)
.width('100%')
.padding(10)
.backgroundColor('#F0F0F0')
.borderRadius(16)
}
}
卡片子组件
@Component
struct CardItemView {
item: CardItem = { id: 0, title: '', color: '#ccc' };
build() {
Column() {
Text(this.item.title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
}
.width('100%')
.aspectRatio(1.0)
.backgroundColor(this.item.color)
.borderRadius(12)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.shadow({ radius: 6, color: 'rgba(0, 0, 0, 0.15)', offsetX: 0, offsetY: 3 })
}
}
完整代码(约 320 行)详见项目
GridAutoFillExample.ets文件。
4.3 代码关键设计说明
为什么选择 minmax(80vp, 1fr)?
- 80vp 是移动端较为舒适的卡片最小宽度。太小会导致文字拥挤,太大则在大屏上无法充分利用空间;
- 1fr 允许卡片在不超过容器宽度的前提下均匀分配剩余空间。
为什么用 aspectRatio(1.0) 而非固定 height?
- 无论列宽如何变化,卡片始终是正方形,视觉统一;
- 无需手动计算高度,减少适配工作量;
- 与 Grid 的自适应列数天然配合——列宽由 Grid 决定,高度自动跟随宽度。
为什么使用独立子组件 CardItemView?
- 职责分离,代码可读性和可维护性更高;
- 类型安全:通过接口
CardItem传递数据,编译时检查; - 可复用性:可在页面其他位置复用。
五、实战经验与性能考量
5.1 常见陷阱
陷阱一:repeat(auto-fill, 80vp) 没有使用 minmax
// ❌ 错误 —— 列宽固定 80vp,不会弹性拉伸
.columnsTemplate('repeat(auto-fill, 80vp)')
// ✅ 正确
.columnsTemplate('repeat(auto-fill, minmax(80vp, 1fr))')
没有 minmax 时,列宽被硬编码。若容器宽度为 410vp,恰好 5 列(5×80=400)后剩余 10vp 变成右侧留白。使用 minmax(80vp, 1fr) 后,这 10vp 会被 5 列平分,每列变为 82vp。
陷阱二:auto-fill 与 auto-fit 的视觉突变
当容器宽度恰好使列数变化时,卡片宽度会发生跳变。在动画场景中(如窗口拖拽),建议加上 .animation() 修饰符来平滑过渡。
陷阱三:没有配合 @State 驱动刷新
Grid 的 columnsTemplate 在组件创建时解析一次。如需动态切换 auto-fill / auto-fit,应通过 if/else 切换两个不同的 Grid 组件,或通过 @State 驱动父组件刷新。
5.2 性能建议
- 避免 GridItem 内嵌套过深:每个 GridItem 渲染一次,嵌套过深会影响首屏性能;
- ForEach 合理设置 key:第三个参数返回唯一标识符(如
item.id.toString()),帮助框架优化列表复用; - 控制 item 数量:单屏建议控制在 50 个以内;超过时使用
LazyForEach配合数据懒加载。
5.3 与 LazyForEach 的配合
当卡片数据量较大时,应将 ForEach 替换为 LazyForEach,只渲染视口内的 GridItem:
Grid() {
LazyForEach(this.dataSource, (item: CardItem) => {
GridItem() { CardItemView({ item: item }) }
}, (item: CardItem) => item.id.toString())
}
.columnsTemplate('repeat(auto-fill, minmax(80vp, 1fr))')
LazyForEach需要数据源实现IDataSource接口。
六、进阶:嵌套 Grid 与混合布局
6.1 标题 + Grid 的典型组合
Column({ space: 12 }) {
Row({ space: 8 }) {
Text('热门推荐').fontSize(18).fontWeight(FontWeight.Bold)
Text('查看更多 →').fontSize(13).fontColor('#0984E3')
}
.width('100%').justifyContent(FlexAlign.SpaceBetween)
AutoFillGrid({ cards: hotItems })
}
.padding(16)
6.2 多组 Grid 在 Scroll 中滚动
Scroll() {
Column({ space: 24 }) {
SectionGrid({ title: '今日推荐', cards: todayItems })
SectionGrid({ title: '热门榜单', cards: hotItems })
SectionGrid({ title: '猜你喜欢', cards: recommendItems })
}
.padding(16)
}
.width('100%')
每组 Grid 独立计算自己的列数,互不干扰,形成「段内网格对齐,段间高度独立」的页面节奏。
七、与其他布局方案的对比
7.1 与 Flex + wrap 的对比
| 维度 | Grid + auto-fill | Flex + wrap |
|---|---|---|
| 对齐 | 严格网格对齐 | 可能参差不齐 |
| 列数控制 | 自动(基于容器宽度) | 手动(百分比) |
| 空位处理 | auto-fill 保留 / auto-fit 折叠 | 无此概念 |
| 性能 | 单次布局计算 | 每行单独计算 |
| 弹性伸缩 | minmax 天然支持 | 需额外 JS 计算 |
7.2 与媒体查询的对比
// 传统媒体查询
if (Display.getWindowWidth() >= 800) { columns = 4 }
else if (Display.getWindowWidth() >= 600) { columns = 3 }
else { columns = 2 }
媒体查询的问题是断点生硬:在 599vp 还是 2 列,在 600vp 突然变成 3 列。而 auto-fill/fit 配合 minmax 可以实现连续、无感的列数变化——容器每增加一个 min 宽度就自动增加一列。
八、API 24 新特性与兼容性说明
8.1 API 24(HarmonyOS NEXT 7.0+)中的变化
-
Slider 组件:在 API 24 中,
Slider、SliderStyle、SliderChangeMode均为全局可用的内置类型,无需import导入。 -
@Component struct 属性可见性:API 24 编译器对
private属性的外部赋值检查更加严格。需要从父组件传入的属性不得标记为private,应使用默认(包级)可见性。 -
Grid 性能优化:API 24 的 Grid 组件内部布局算法经过优化,对于
auto-fill/fit模式下列数频繁变化的场景,性能提升了约 30%。
8.2 向后兼容
// API 18+ 均支持
.columnsTemplate('repeat(auto-fill, minmax(80vp, 1fr))')
repeat(auto-fill / auto-fit) 语法自 API 18(HarmonyOS 4.1) 起已稳定支持。API 24 在此基础上做了性能优化和类型安全增强。如果你的项目目标 API 18~23,以上代码无需修改即可运行。
九、总结与建议
9.1 一句话总结
repeat(auto-fill/fit, minmax(min, 1fr)) 是鸿蒙 ArkTS 中最优雅、最强大的自适应列数布局方案,没有之一。
9.2 选型决策树
需要网格布局吗?
├── 列数固定 → repeat(count, expr)
└── 列数自适应 → 选择 auto-fill 还是 auto-fit
├── 需要空位占位保持对齐? → auto-fill
└── 希望内容尽量铺满? → auto-fit
9.3 推荐阅读
- HarmonyOS 官方文档:Grid 组件
- ArkTS 语法参考:@Component 装饰器
- 响应式布局指南:ArkTS 自适应布局
本文配套示例代码位于项目 entry/src/main/ets/pages/GridAutoFillExample.ets,可直接在 DevEco Studio 中打开并预览。
更多推荐



所有评论(0)