承接上一节媒体查询的核心能力,本节我们将系统学习鸿蒙官方响应式布局核心组件GridRow/GridCol,搞定「一套代码,多设备适配」的标准化解决方案。

【学习目标】

  • 掌握 GridRow/GridCol 核心能力,理解栅格断点与媒体查询的联动逻辑
  • 吃透 GridRow 四大核心配置:断点规则、总列数、排列方向、子组件间距的完整用法
  • 掌握 GridCol 三大核心能力:span 占用列数、offset 偏移列数、order 排序规则
  • 实现 手机/平板/折叠屏/横竖屏 全场景自适应布局,一套代码适配全尺寸设备
  • 理解栅格嵌套用法,结合媒体查询工具类,完成复杂页面的响应式开发

一、工程目录结构

复用上一节MediaQueryDemo工程,目录结构如下:

MediaQueryDemo/
├── entry/src/main/ets/
│   ├── utils/
│   │   └── MediaQueryUtil.ets      // 上一节封装的媒体查询工具类
│   └── pages/
│       ├── Index.ets               // 本节:3个示例导航入口(新)
│       ├── GridRowBaseDemo.ets     // 本节:栅格基础示例(新)
│       ├── GridRowNestDemo.ets     // 本节:栅格嵌套示例(新)
│       └── GridRowDemo.ets         // 本节:电商首页综合实战(新)
└── resources/                      // 工程资源目录

二、栅格布局核心基础

2.1 什么是栅格布局

栅格布局是鸿蒙官方提供的响应式布局标准化组件,以「行(GridRow)+ 列(GridCol)」为核心,将页面划分为等宽的列数,通过断点规则监听设备宽度变化,动态修改子组件的占位、偏移、排序,彻底解决多尺寸多设备的动态布局问题。

核心规则:GridRow 为栅格容器组件,必须与栅格子组件 GridCol 联合使用,单独使用不生效。

2.2 栅格布局核心适配逻辑

  1. 容器 GridRow 监听设备/窗口宽度变化,触发断点切换
  2. 断点切换后,子组件 GridCol 自动更新占位、偏移、排序规则
  3. 子组件超出一行总列数时,自动换行适配,无需手动处理布局逻辑

三、栅格容器 GridRow详解

GridRow 是栅格布局的核心容器,负责定义全局布局规则、断点规则、列数规则,所有配置均支持断点差异化设置。

3.1 构造函数语法

GridRow(option?: {
  breakpoints?: BreakpointsOptions;  // 断点规则配置
  columns?: number | GridRowColumnOption;  // 布局总列数
  direction?: GridRowDirection;  // 子组件排列方向
  gutter?: number | GutterOption;  // 子组件间距
})

3.2 断点规则 breakpoints

栅格布局以设备水平宽度(单位vp) 作为断点依据,将设备宽度划分为不同区间,可针对不同区间设置差异化布局规则,与上一节媒体查询断点完全同源。

3.2.1 默认断点规则

鸿蒙栅格默认提供4个断点,将设备宽度分为4类,无需额外配置即可使用:

断点名称 取值范围(vp) 设备描述
xs [0, 320) 最小宽度类型设备
sm [320, 600) 小宽度类型设备(手机竖屏)
md [600, 840) 中等宽度类型设备(折叠屏、平板竖屏)
lg [840, +∞) 大宽度类型设备(平板横屏、车机)
3.2.2 自定义断点规则

支持最多6个断点(xs、sm、md、lg、xl、xxl),通过单调递增数组设置断点位置,数组长度最大为5,对应6个断点区间,规则如下:

传入数组 断点区间划分
[n0, n1, n2, n3, n4] xs: [0, n0)、sm: [n0, n1)、md: [n1, n2)、lg: [n2, n3)、xl: [n3, n4)、xxl: [n4, +∞)

核心配置项

  • value:断点数值数组,必须单调递增,单位必须加vp,否则不生效
  • reference:断点切换参照物,可选BreakpointsReference.WindowSize(应用窗口宽度,推荐)、BreakpointsReference.ComponentSize(组件自身宽度)

代码示例

// 自定义断点,覆盖手机、折叠屏、平板、大屏设备
GridRow({
  breakpoints: {
    value: ['320vp', '600vp', '840vp', '1440vp', '1600vp'],
    reference: BreakpointsReference.WindowSize
  }
}) {
  // 栅格子组件GridCol
}
3.2.3 断点变化监听事件

通过 onBreakpointChange 监听断点切换,可联动页面样式、逻辑更新,与上一节媒体查询工具类完美配合:

GridRow() {
  // 子组件内容
}
.onBreakpointChange((breakpoint: string) => {
  console.info(`当前断点切换为:${breakpoint}`);
  // 断点切换后联动更新页面逻辑
})

3.3 布局总列数 columns

定义栅格容器在不同断点下的总列数,是栅格布局的核心基准。

  • API 20之前:columns默认值为12,即在未设置columns时,任何断点下,栅格布局均被分成12列。
  • API 20及以后:columns默认值为{ xs: 2, sm: 4, md: 8, lg: 12, xl: 12, xxl: 12 }。
3.3.1 固定列数配置

所有断点下使用统一的总列数,适用于简单布局场景:

// 所有设备均分为8列
GridRow({ columns: 8 }) {}
3.3.2 断点差异化列数配置

针对不同断点设置不同总列数,未设置的断点遵循继承规则

  • 小于已设置的最小断点:使用默认值12列
  • 大于已设置的最大断点:继承前一个断点的列数值

代码示例

GridRow({
  // 小屏4列、中屏8列、大屏12列
  columns: { sm: 4, md: 8, lg: 12 },
  breakpoints: {
    value: ['320vp', '600vp', '840vp']
  }
}) {
  // 子组件内容
}

继承规则说明:上述配置中,xs断点不配置API20会签默认12列,API20之后默认继承sm栅格数量。

3.4 排列方向 direction

定义栅格子组件在容器中的排列方向,默认从左到右排列:

枚举值 说明
GridRowDirection.Row 默认值,子组件从左往右排列
GridRowDirection.RowReverse 子组件从右往左反向排列

代码示例

// 反向排列
GridRow({ direction: GridRowDirection.RowReverse }) {}

3.5 子组件间距 gutter

定义栅格子组件之间的水平、垂直间距,避免子组件紧贴在一起,提升布局美观度。

3.5.1 统一间距配置

水平、垂直方向使用相同间距:

// 子组件水平、垂直间距均为10vp
GridRow({ gutter: 10 }) {}
3.5.2 差异化间距配置

单独设置水平、垂直方向的间距:

// 水平间距20vp,垂直间距15vp
GridRow({ gutter: { x: 20, y: 15 } }) {}

四、栅格子组件 GridCol

GridCol 是栅格布局的子组件,必须作为 GridRow 的直接子组件使用,负责定义单个元素在不同断点下的占位、偏移、排序规则。

4.1 构造函数语法

GridCol(option?: {
  span?: number | GridColColumnOption;  // 占用列数
  offset?: number | GridColColumnOption;  // 偏移列数
  order?: number | GridColColumnOption;  // 排序序号
})

4.2 span 占用列数

定义子组件在栅格容器中占用的列数,默认值为1,决定了子组件的宽度,是栅格布局最核心的属性。

4.2.1 固定占用列数

所有断点下占用相同列数:

GridRow({ columns: 12 }) {
  // 所有设备均占用2列
  GridCol({ span: 2 }) {
    Text('固定占位')
  }
}
4.2.2 断点差异化占用列数

针对不同断点设置不同占用列数,实现响应式适配,一行内子组件span总和超过容器总列数时,自动换行

4.3 offset 偏移列数

定义子组件相对于前一个子组件的偏移列数,默认值为0,用于实现留白、居中、两端对齐等布局效果。

4.3.1 固定偏移列数

所有断点下偏移相同列数:

GridRow({ columns: 12 }) {
  // 前一个子组件
  GridCol({ span: 4 }) {
    Text('左侧')
  }
  // 偏移2列,再占用4列,实现左右留白
  GridCol({ span: 4, offset: 2 }) {
    Text('右侧')
  }
}
4.3.2 断点差异化偏移列数

针对不同断点设置不同偏移量,适配不同设备的布局留白:

GridRow({ columns: 12 }) {
  GridCol({
    // 手机端占10列、左右各偏移1列居中;平板端占8列、左右各偏移2列居中
    span: { sm: 10, md: 8 },
    offset: { sm: 1, md: 2 }
  }) {
    Text('居中内容')
      .width('100%')
      .height(100)
      .textAlign(TextAlign.Center)
      .backgroundColor('#FFFFFF')
  }
}

关键注意点:offset是累计偏移,会占用栅格总列数,需保证「前一个子组件span + 当前offset + 当前span ≤ 总列数」,否则会自动换行。

4.4 order 排序序号

定义子组件的排列次序,默认按代码书写顺序排列,order数值越小,组件排列越靠前,可实现不同设备下的元素顺序重排。

4.4.1 排序规则
  1. 子组件未设置order或设置相同order,按代码书写顺序排列
  2. 子组件设置不同order,按数值从小到大排列
  3. 部分设置order、部分未设置,未设置order的组件按代码顺序排在最前面,设置了order的组件再按数值排序

4.5 基础示例代码

代码示例

import { GridBreakpointType, MediaQueryUtil } from '../utils/MediaQueryUtil';

@Entry
@Component
struct GridRowBaseDemo {
  @State currentBreakpoint: string = 'xs';
  private mediaUtil = MediaQueryUtil.getInstance();

  aboutToAppear(): void {
    this.initMediaQuery();
  }

  private initMediaQuery(): void {
    this.mediaUtil.init(this.getUIContext());
    this.mediaUtil.onBreakpointChange((breakpoint: GridBreakpointType) => {
      this.currentBreakpoint = breakpoint;
    });
  }

  aboutToDisappear(): void {
    this.mediaUtil.offBreakpointChange();
  }

  // order 排序演示
  @Builder
  orderDemoBuilder() {
    Text("1. order 排序演示(小屏正序 / 大屏倒序)")
      .fontSize(18)
      .fontWeight(FontWeight.Bold)
      .margin({ bottom: 10 });

    GridRow({ columns: 12, gutter: 10 }) {
      GridCol({ span: 3, order: { sm: 1, lg: 4 } }) {
        Text('1')
          .width('100%')
          .height(50)
          .textAlign(TextAlign.Center)
          .backgroundColor('#FF3B30')
      }
      GridCol({ span: 3, order: { sm: 2, lg: 3 } }) {
        Text('2')
          .width('100%')
          .height(50)
          .textAlign(TextAlign.Center)
          .backgroundColor('#FF9500')
      }
      GridCol({ span: 3, order: { sm: 3, lg: 2 } }) {
        Text('3')
          .width('100%')
          .height(50)
          .textAlign(TextAlign.Center)
          .backgroundColor('#34C759')
      }
      GridCol({ span: 3, order: { sm: 4, lg: 1 } }) {
        Text('4')
          .width('100%')
          .height(50)
          .textAlign(TextAlign.Center)
          .backgroundColor('#007AFF')
      }
    }
    .margin({ bottom: 20 });
  }

  // 商品栅格演示
  @Builder
  goodsGridBuilder() {
    Text("2. 多设备响应式栅格演示")
      .fontSize(18)
      .fontWeight(FontWeight.Bold)
      .margin({ top: 10, bottom: 10 });

    GridRow({
      columns: 12,
      breakpoints: {
        value: ['320vp', '600vp', '840vp', '1440vp'],
        reference: BreakpointsReference.WindowSize
      },
      gutter: { x: 15, y: 15 }
    }) {
      ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], (index: number) => {
        GridCol({
          span: {
            xs: 12,
            sm: 6,
            md: 4,
            lg: 3,
            xl: 2,
            xxl: 2
          }
        }) {
          Row() {
            Text(`商品${index}`)
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .fontColor($r('sys.color.ohos_id_color_text_primary'))
          }
          .width('100%')
          .height(100)
          .justifyContent(FlexAlign.Center)
          .backgroundColor($r('sys.color.ohos_id_color_sub_background'))
          .borderRadius(12)
          .shadow({ radius: 3, color: '#00000010' })
        }
      })
    }
  }

  build() {
    Column() {
      // 当前断点
      Text("当前设备规格:" + this.currentBreakpoint)
        .fontSize(20)
        .fontWeight(FontWeight.Medium)
        .fontColor($r('sys.color.ohos_id_color_text_primary'))
        .margin({ bottom: 15 });

      // 调用自定义构造函数
      this.orderDemoBuilder();
      this.goodsGridBuilder();
    }
    .padding(20)
    .width('100%')
    .height('100%')
    .backgroundColor($r('sys.color.ohos_id_color_background'))
  }
}

运行效果

  1. order 排序演示效果

    • 小屏(sm 断点):组件按代码顺序显示:1 → 2 → 3 → 4
    • 大屏(lg 断点):组件按 order 配置倒序显示:4 → 3 → 2 → 1
    • 无需修改页面结构,仅通过属性配置即可实现不同设备下的显示顺序动态调整
  2. 多设备响应式商品栅格效果

    • xs 超小屏:一行显示 1 个商品,宽度占满整行
    • sm 手机竖屏:一行显示 2 个商品,均匀分布
    • md 平板竖屏:一行显示 3 个商品,布局规整
    • lg 平板横屏:一行显示 4 个商品,充分利用大屏空间
    • xl/xxl 超大屏:一行显示 6 个商品,高密度布局
    • 所有设备下卡片间距统一、宽高比一致,自动适配无变形、无溢出
  3. offset 偏移演示效果

    • 为 GridCol 设置 span: 3 + offset: 1,单个组件实际占用 4 列
    • 在 12 列栅格中一行可整齐排列 3 个组件
    • 组件自动留白错位排列,布局层次清晰,无重叠、无截断
    • 可快速实现居中布局、间隔布局等常见排版效果

效果展示

order 排序演示效果 多设备响应式商品栅格效果 offset 偏移演示效果
Grid基础1 Grid基础2 Grid基础3

五、栅格组件的嵌套使用

栅格组件支持嵌套使用,用于实现复杂页面的精细化布局,核心规则:嵌套的子GridRow的列数基准,是父GridCol的宽度,而非全局屏幕宽度

嵌套示例

@Entry
@Component
struct GridRowNestDemo {

  build() {
    // 最外层:全局栅格布局
    GridRow({ columns: 12, gutter: { x: 10, y: 10 } }) {
      // 1. 主体内容区域(嵌套栅格)
      GridCol({ span: 12 }) {
        this.MainContentArea();
      }

      // 2. 底部导航区域
      GridCol({ span: 12 }) {
        this.FooterArea();
      }
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#FFFFFF')
  }

  /**
   * 主体内容区域(内部嵌套第二层栅格)
   * 包含:侧边栏 + 内容区
   */
  @Builder
  MainContentArea() {
    GridRow({ columns: 12, gutter: 10 })
    {
      // 小于 lg 时 span: 0 → 隐藏
      // 大于等于 lg 时 span: 2 → 显示
      GridCol({
        span: { xs: 0, md: 0, lg: 2 }
      }) {
        this.Sidebar();
      }

      // 内容区:小屏占满12列,大屏占10列
      GridCol({
        span: { xs: 12, md: 12, lg: 10 }
      }) {
        this.ContentBody();
      }
    }.width('100%')
    .height(400)
    .backgroundColor('#F5F5F5')
    .borderRadius(12)
  }

  /** 侧边栏 */
  @Builder
  Sidebar() {
    Row() {
      Text('侧边栏')
        .fontSize(20)
        .fontColor('#FFFFFF')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#007AFF')
    .borderRadius(12)
  }

  /** 内容主体 */
  @Builder
  ContentBody() {
    Row() {
      Text('内容主体')
        .fontSize(20)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#34C759')
    .borderRadius(12)
  }

  /** 底部导航 */
  @Builder
  FooterArea() {
    Row() {
      Text('底部导航')
        .fontSize(18)
        .fontColor('#FFFFFF')
    }
    .width('100%')
    .height(60)
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#1D1D1F')
    .borderRadius(12)
  }
}

运行效果

  • 小屏/中屏(xs/md):侧边栏隐藏(span:0),内容区域占满全部宽度,页面简洁适配手机
  • 大屏(lg 及以上):侧边栏显示(span:2),内容区域自适应占 10 列,呈现 PC 端经典布局
  • 嵌套栅格以父容器宽度为 12 列基准,布局层级清晰,内外栅格互不干扰
  • 底部导航固定占满整行,视觉统一,适配全尺寸设备

效果展示

侧边栏显示 侧边栏隐藏
Grid-lg侧边栏显示 Grid-md侧边栏隐藏

六、电商首页全场景响应式开发

结合上一节的MediaQueryUtil媒体查询工具类,实现一个完整的电商首页,支持横竖屏切换、深浅色模式适配、手机/平板/折叠屏全设备适配,可直接复制运行。

完整代码(pages/GridRowDemo.ets)

import { MediaQueryUtil, MediaFullState } from '../utils/MediaQueryUtil';

@Entry
@Component
struct GridRowDemo {
  // 响应式状态(复用媒体查询工具类)
  @State isDarkMode: boolean = false;
  @State currentBreakpoint: string = 'sm';

  // 主题配色
  @State pageBgColor: string = '#F5F5F5';
  @State cardBgColor: string = '#FFFFFF';
  @State textColor: string = '#000000';
  @State primaryColor: string = '#FF3B30';

  // 模拟分类数据
  private categoryList: string[] = ['手机', '平板', '电脑', '穿戴', '耳机', '电视', '家居', '配件'];
  // 模拟商品数据
  private goodsList: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

  aboutToAppear(): void {
    // 初始化媒体查询工具类,联动栅格断点
    const mediaUtil = MediaQueryUtil.getInstance();
    mediaUtil.init(this.getUIContext());

    mediaUtil.onFullStateChange((state: MediaFullState) => {
      this.isDarkMode = state.isDarkMode;
      this.currentBreakpoint = state.breakpoint;
      this.updateTheme(state.isDarkMode);
    });
  }

  // 导航栏(修复水平对齐 + 保留原有样式)
  @Builder
  navigationBarBuilder() {
    GridRow({
      columns: 12,
      gutter: { x: 10, y: 12 },
      breakpoints: { reference: BreakpointsReference.WindowSize },
    }) {
      // ========== 第一行 ==========
      // 商城标题:sm占6列,md/lg占2列
      GridCol({ span: { sm: 6, md: 2, lg: 2 } }) {
        Row(){
          Text('鸿蒙商城')
            .fontSize(20)
            .height(36)
            .fontWeight(FontWeight.Bold)
            .fontColor(this.primaryColor)
        }
        .width('100%')
        .justifyContent(FlexAlign.Start)
      }
      // 购物车sm小屏
      GridCol({ span: { sm: 6, md: 0, lg: 0 } }) {
        Row() {
          Button('购物车',{buttonStyle:ButtonStyleMode.NORMAL})
            .fontSize(14)
            .height(36) // 固定按钮高度
            .fontColor(this.cardBgColor)
            .backgroundColor(this.primaryColor)
        }
        .justifyContent(FlexAlign.End)
        .width('100%')
      }

      // ========== 第二行(sm占12列,md/lg拆分) ==========
      GridCol({ span: { sm: 12, md: 6, lg: 8 } }) {
         Row(){
           Search({placeholder:'搜索商品'})
             .width('100%')
             .backgroundColor(this.pageBgColor)
             .placeholderColor('#999999')
             .margin(0)
         }.width('100%')
      }

      // 购物车(大屏)
      GridCol({ span: { sm: 0, md: 4, lg: 2 } }) {
        Row() {
          Button('购物车',{stateEffect:true,buttonStyle:ButtonStyleMode.NORMAL})
            .fontSize(14)
            .height(36) // 固定按钮高度
            .padding({left:20,right:20})
            .fontColor(this.cardBgColor)
            .backgroundColor(this.primaryColor)
        }
        .justifyContent(FlexAlign.End)
        .width('100%')
      }
    }
    .width('100%')
    .padding({ left: 15, right: 15, top: 12, bottom: 12 })
    .backgroundColor(this.cardBgColor)
  }

  // 轮播
  @Builder
  bannerBuilder() {
    // 2. 轮播Banner区域
    GridRow({ columns: 12 }) {
      GridCol({ span: 12 }) {
        Row() {
          Text('首页轮播图')
            .fontSize(18)
            .fontColor($r('sys.color.comp_background_list_card'))
            .fontWeight(FontWeight.Medium)
        }
        .width('100%')
        .height(this.currentBreakpoint === 'sm' ? 150 : 200)
        .justifyContent(FlexAlign.Center)
        .backgroundColor(this.primaryColor)
        .borderRadius(12)
      }
    }
    .width('92%')
    .margin({ top: 15 })

  }
  // 分类
  @Builder
  categorySectionBuilder() {
    // 3. 分类入口区域
    Text('全部分类')
      .fontSize(18)
      .fontWeight(FontWeight.Bold)
      .fontColor(this.textColor)
      .alignSelf(ItemAlign.Start)
      .margin({ left: '4%', top: 20, bottom: 10 })

    GridRow({
      columns: 8,
      gutter: { x: 10, y: 15 },
      breakpoints: { reference: BreakpointsReference.WindowSize }
    }) {
      ForEach(this.categoryList, (item: string) => {
        GridCol({ span: { sm: 2, md: 1 } }) {
          Column({ space: 8 }) {
            Row() {
              Text(item[0])
                .fontSize(18)
                .fontColor($r('sys.color.comp_background_list_card'))
            }
            .width(40)
            .height(40)
            .borderRadius(20)
            .backgroundColor(this.primaryColor)
            .justifyContent(FlexAlign.Center)

            Text(item)
              .fontSize(12)
              .fontColor(this.textColor)
          }
          .width('100%')
        }
      })
    }
    .width('92%')
  }
  // 商品列表
  @Builder
  goodsListBuilder() {

    // 4. 商品网格区域
    Text('热门商品')
      .fontSize(18)
      .fontWeight(FontWeight.Bold)
      .fontColor(this.textColor)
      .alignSelf(ItemAlign.Start)
      .margin({ left: '4%', top: 20, bottom: 10 })

    GridRow({
      columns: 12,
      gutter: { x: 12, y: 12 },
      breakpoints: { reference: BreakpointsReference.WindowSize }
    }) {
      ForEach(this.goodsList, (index: number) => {
        GridCol({
          // 手机竖屏2列、平板竖屏3列、平板横屏4列、大屏6列
          span: { sm: 6, md: 4, lg: 3, xl: 2 }
        }) {
          Column({ space: 8 }) {
            // 商品图片
            Row() {
              Text(`商品${index}`)
                .fontSize(14)
                .fontColor($r('sys.color.comp_background_list_card'))
            }
            .width('100%')
            .aspectRatio(1)
            .backgroundColor('#E0E0E0')
            .borderRadius(12)
            .justifyContent(FlexAlign.Center)

            // 商品名称
            Text(`鸿蒙旗舰商品${index}`)
              .fontSize(14)
              .fontColor(this.textColor)
              .maxLines(1)
              .textOverflow({ overflow: TextOverflow.Ellipsis })
              .width('100%')

            // 商品价格
            Text(`¥${99 + index * 10}.00`)
              .fontSize(16)
              .fontColor(this.primaryColor)
              .fontWeight(FontWeight.Bold)
              .width('100%')
          }
          .width('100%')
          .padding(10)
          .backgroundColor(this.cardBgColor)
          .borderRadius(12)
        }
      })
    }
    .width('92%')
    .margin({ bottom: 20 })
  }
  build() {
    Column(){
      this.navigationBarBuilder()
      Scroll() {
        Column(){
          this.bannerBuilder()
          this.categorySectionBuilder()
          this.goodsListBuilder()
        } .width('100%')
        .layoutWeight(1)
       .backgroundColor(this.cardBgColor)
      }
      .width('100%')
      .height('100%')
    }
  }

  // 更新深浅色主题
  private updateTheme(isDark: boolean): void {
    if (isDark) {
      this.pageBgColor = '#121212';
      this.cardBgColor = '#1E1E1E';
      this.textColor = '#FFFFFF';
    } else {
      this.pageBgColor = '#F5F5F5';
      this.cardBgColor = '#FFFFFF';
      this.textColor = '#000000';
    }
  }

  // 页面销毁时解绑监听,避免内存泄漏
  aboutToDisappear(): void {
    MediaQueryUtil.getInstance().removeAllListeners();
  }
}

运行效果

  1. 顶部导航栏

    • 小屏:购物车、Logo一行,搜索单独一行。
    • 大屏:Logo、购物车、搜索条一行布局。
    • 深浅色模式下导航栏背景、文字颜色自动切换
  2. 轮播Banner

    • 手机竖屏高度自动压缩为 150vp
    • 平板/大屏高度自动扩展为 200vp
    • 圆角、配色统一,视觉效果一致
  3. 分类图标区

    • 小屏一行显示 4 个分类
    • 大屏一行显示 8 个分类
    • 图标+文字垂直居中,布局整齐
  4. 商品网格

    • 手机竖屏:一行 2 列
    • 平板竖屏:一行 3 列
    • 平板横屏:一行 4 列
    • 超大屏设备:一行 6 列
    • 商品卡片自适应缩放,图片区域等比例展示,价格文字突出显示
  5. 深浅色模式

    • 浅色模式:白底+浅灰背景,清爽明亮
    • 深色模式:深灰+黑底,护眼高对比
    • 系统主题切换时页面样式实时同步

效果展示

Grid自适应

七、最佳实践与常见坑点

7.1 开发最佳实践

  1. 断点统一规范:栅格断点与媒体查询断点保持一致,推荐使用官方默认断点+扩展xl/xxl,避免多套规则混乱
  2. 列数规范:优先使用默认12列,12列可被2/3/4/6整除,适配性最强,无特殊需求不自定义列数
  3. 间距规范:水平间距gutter.x推荐使用10-20vp,垂直间距gutter.y推荐使用15-30vp,保证视觉一致性
  4. 参照物规范:断点reference优先使用WindowSize,适配分屏、自由窗口、横屏切换等场景
  5. 嵌套规范:嵌套层级不超过3层,避免布局性能损耗与逻辑混乱

7.2 适用场景

  • 适用于需要一套代码实现多设备自适应的页面开发,可根据手机、平板、折叠屏及横竖屏状态自动调整布局结构。
  • 常用于首页模块化布局,如轮播区、分类金刚区、商品宫格、功能入口卡片等规整排列的场景。
  • 适用于需要严格按照列数划分宽度、统一页面视觉规范的项目。
  • 适用于页面模块需要动态调整**占位宽度(span)、偏移距离(offset)、显示顺序(order)**的响应式场景。
  • 配合 Scroll 组件可实现整页滚动,适合用于内容条数有限、结构清晰的首页、运营活动页、个人中心等页面。

注意:不适合场景

  • 不适合数据量较大的长列表场景,如聊天列表、订单列表、无限加载列表等。
    GridRow 会一次性渲染所有子组件,无列表复用机制,数据过多时容易出现卡顿、掉帧、内存占用过高问题,长列表优先使用 List 组件、瀑布流(WaterFlow)、网格 (Grid/GridItem)等组件。

7.3 高频踩坑避坑指南

  1. 错误写法:断点数组未加vp单位,导致断点不生效
    正确写法value: ['320vp', '600vp', '840vp']

  2. 错误写法:断点数组非单调递增,导致布局异常
    正确规则:数组必须从小到大依次递增,不可出现降序、相等数值

  3. 错误写法:单独使用GridRow不搭配GridCol,导致布局不生效
    正确规则GridRow必须与GridCol联合使用,单独使用无布局效果

  4. 错误认知:嵌套栅格的列数基准 = 全局屏幕宽度
    正确规则:嵌套GridRow的列数基准是父级GridCol的宽度,而非全局屏幕

  5. 错误布局:一行内span + offset总和超过总列数,导致意外换行
    正确规则:布局前计算好总列数,保证
    子组件span总数 + 偏移offset总数 ≤ 容器总列数

  6. 内存泄漏:页面销毁时未解绑媒体查询监听
    正确规则:在aboutToDisappear生命周期中必须调用removeAllListeners()

  7. 滚动失效:直接使用GridRow希望实现内容滚动
    正确规则GridRow不支持滚动,真实开发必须使用
    Scroll + GridRow组合实现滚动布局

  8. 高度异常:给GridRow设置固定高度导致内容被截断
    正确规则GridRow高度由内容自动撑开,不手动写死高度,滚动交给Scroll

八、内容总结

  1. 核心定位GridRow/GridCol 是鸿蒙官方响应式布局标准化方案,基于媒体查询能力封装,解决多设备适配痛点
  2. 核心架构GridRow 负责定义全局规则(断点、列数、间距、方向),GridCol 负责定义单个元素的响应式规则(span、offset、order)
  3. 核心能力:通过断点规则实现不同设备下的布局动态调整,支持列数、占位、偏移、排序的断点差异化配置
  4. 工程落地:与媒体查询工具类配合使用,可实现布局+样式+主题的全维度响应式,一套代码适配全场景

九、下节预告

下一节我们将进入*Grid网格布局与不规则宫格实战**。

  • 掌握 Grid/GridItem 基础用法、行列模板、间距、对齐方式
  • 实现不规则宫格布局(跨行、跨列、阶梯布局、首页金刚区)
  • 完成常见的九宫格、功能入口、卡片组合实战案例
Logo

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

更多推荐