鸿蒙原生 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('...')      // 行模板(可选)

columnsTemplaterowsTemplate 接受一个轨道描述字符串,遵循 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+)中的变化

  1. Slider 组件:在 API 24 中,SliderSliderStyleSliderChangeMode 均为全局可用的内置类型,无需 import 导入。

  2. @Component struct 属性可见性:API 24 编译器对 private 属性的外部赋值检查更加严格。需要从父组件传入的属性不得标记为 private,应使用默认(包级)可见性。

  3. 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 推荐阅读


本文配套示例代码位于项目 entry/src/main/ets/pages/GridAutoFillExample.ets,可直接在 DevEco Studio 中打开并预览。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐