前言

项目从旧版本升级到鸿蒙 6 API 20 之后,栅格布局很容易出现一种情况,代码没有明显报错,页面排版却突然变了。小屏设备上,原来一行能放下的内容挤成了两列。某些卡片原本只占一小块宽度,升级后直接撑成了半屏。

问题通常出在 GridRow.columnsGridCol.span 这两组默认行为上。API 20 之后,GridRow.columns 的默认值从固定的 12 改成了响应式对象 { xs: 2, sm: 4, md: 8, lg: 12, xl: 12, xxl: 12 }。同时,GridRow.columnsGridCol.span 在只配置部分断点时,补全规则也一起变了。

这类变更影响的范围不小。很多旧代码默认按照 12 列思路写布局,GridCol() 也经常直接使用默认占位。API 20 之后,列数跟着断点走,默认占位对应的实际宽度也会一起变。页面如果原来就依赖默认行为,升级后最容易出问题。

一、先把 API 20 改动的核心看清楚

最先要确认的是 GridRow.columns。旧版本里,columns 不写时默认就是 12。API 20 之后,默认值变成响应式对象,xs 断点下是 2 列,sm 是 4 列,md 是 8 列,到了 lgxlxxl 才回到 12 列。这个改动已经写进当前栅格布局文档和 6.0.0(20) Beta1 的行为变更说明里。

第二个变化出在断点补全规则。GridRow.columns 如果只配置了部分断点,旧规则会优先拿已配置的更小断点补全,找不到更小断点时回退到默认值 12。API 20 之后,找不到更小断点时,会继续用已配置的更大断点来补全。GridCol.span 也做了同样方向的调整。旧规则找不到更小断点时回退默认值 1,API 20 之后会优先拿已配置的更大断点补全。这个变化会直接影响小屏设备的表现。

这就是很多页面升级后突然变形的根源。旧布局里,开发者没有显式写全断点,系统一直按旧规则补。升级到 API 20 之后,补全逻辑变了,最终落到每个断点上的列数和占位也跟着变。

二、旧代码为什么会在小屏上突然变形

很多老项目里,GridRow() 直接不写 columns,默认按 12 列来算。GridCol() 也经常不写 span,或者只给少数断点补一个值。这样的代码在旧规则下能跑出预期,是因为整套默认行为都围绕 12 列展开。API 20 之后,小屏断点的默认列数只有 2 或 4,整个版式会马上变紧。

这里最容易中招的是这种写法。开发者以为 GridCol() 只是一个小占位,实际上它会跟着当前断点的列数一起换算宽度。旧文档里,span 默认值是 1。这个默认值本身没有陌生感,问题在于一列到底有多宽,取决于 GridRow.columns 当前是多少列。12 列体系里的一列,和 2 列体系里的一列,差别很大。

举个最常见的例子。一个旧页面写成 GridRow() 加几个 GridCol(),开发者脑子里默认是一行 12 列,小组件只占 1 列,宽组件占 3 列或 6 列。升级后到了 xs 断点,默认只剩 2 列,原来的相对关系会整体放大。页面看起来像被重新排过一次版,其实代码根本没动。

三、迁移时有两条路,先选一条再动代码

第一条路最稳,适合老项目。做法很简单,把旧页面依赖的 12 列逻辑显式写出来,不再交给默认值决定。这样升级后页面能先稳住,后面再慢慢做响应式改造。

@Entry
@Component
struct LegacyStableLayout {
  build() {
    GridRow({ columns: 12 }) {
      GridCol() {
        Text('项目1')
      }
      GridCol({ span: 6 }) {
        Text('项目2')
      }
      GridCol({ span: 3 }) {
        Text('项目3')
      }
    }
  }
}

这种写法的优点很直接,历史布局不会因为默认值变化而突然跑掉。旧项目如果页面很多,这通常是第一步。先把显式 columns: 12 补上,再看哪些页面值得继续做响应式优化。GridRow.columns 在 API 20 之后默认行为已经改了,显式写值是最省事的止损方式。

第二条路适合新页面,或者准备顺手重做响应式布局的模块。做法是接受 API 20 的默认断点体系,把 span 按断点写完整,让每个尺寸下的占位都可读、可控。

@Entry
@Component
struct ResponsiveLayout {
  build() {
    GridRow() {
      GridCol({
        span: { xs: 1, sm: 1, md: 1, lg: 1, xl: 1, xxl: 1 }
      }) {
        Text('项目1')
      }

      GridCol({
        span: { xs: 2, sm: 2, md: 3, lg: 6, xl: 6, xxl: 6 }
      }) {
        Text('项目2')
      }

      GridCol({
        span: { xs: 2, sm: 2, md: 2, lg: 3, xl: 3, xxl: 3 }
      }) {
        Text('项目3')
      }
    }
  }
}

这条路更适合需要长期维护的页面。列数交给断点,span 自己写全,页面在小屏、中屏、大屏上会更稳定。鸿蒙栅格默认提供 xssmmdlg 四个断点,还支持启用 xlxxl。如果项目有折叠屏、平板或多窗口场景,这种写法后面会轻松很多。

四、迁移后怎么排查和收口

这类升级最怕隐性问题。页面在主力测试机上看着正常,换一台更小的设备或者分屏窗口,布局才开始乱。处理这类问题,最稳的办法还是把默认行为收掉。旧页面统一补 columns: 12,新页面统一把 span 断点写全,项目里的栅格风格会快很多收住。

如果项目准备长期跟着 API 20 这套响应式逻辑走,建议把断点列数提成一份集中配置,不要在各个页面里手写一堆数字。这样后面统一调整规则时,改动点会少很多。官方栅格布局文档也支持开发者自定义断点范围,最多支持 6 个断点;默认推荐值仍然是 xssmmdlg 这套范围。

可以这样写一个简单的配置模块:

export const GRID_COLUMNS = {
  xs: 2,
  sm: 4,
  md: 8,
  lg: 12,
  xl: 12,
  xxl: 12
} as const

@Entry
@Component
struct ConfiguredGridLayout {
  build() {
    GridRow({ columns: GRID_COLUMNS }) {
      GridCol({ span: { xs: 2, sm: 2, md: 4, lg: 4, xl: 4, xxl: 4 } }) {
        Text('响应式内容')
      }
    }
  }
}

排查工具上,DevEco Studio 的 ArkUI Inspector 可以直接看真机布局效果和界面状态变化,适合用来定位升级后的宽度异常、占位异常和断点切换问题。页面如果升级前后变化较大,优先用它对比真机表现,效率会比肉眼扫代码高很多。

总结

这次 API 20 的栅格变更,重点落在两件事上。第一,GridRow.columns 的默认值从固定 12 列改成了响应式对象。第二,GridRow.columnsGridCol.span 在只写部分断点时,补全规则一起调整了。页面一旦依赖旧默认行为,升级后就很容易在小屏和分屏场景里变形。

迁移时更稳的做法只有两条。老项目先把 columns: 12 显式补上,先保住旧布局。准备做响应式升级的页面,把 span 断点写完整,跟着 API 20 的断点体系走。这样处理之后,页面的宽度关系会重新回到可控状态,后面的适配成本也会低很多。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐