在 HarmonyOS 应用开发中,如何让界面在手机、平板、电视等不同设备上都保持美观一致的布局?今天要给大家介绍的 GridRow 组件就是解决这个问题的 "神器"。作为 HarmonyOS 提供的栅格布局容器,它能帮我们轻松实现多设备自适应布局,让界面在各种屏幕尺寸下都能有规律、有秩序地展示。接下来,我们就用最接地气的方式,带大家全面掌握 GridRow 的用法,每个知识点都配上实战代码,保证一看就懂、一学就会!

一、GridRow 到底是个啥?

GridRow 其实就是一个栅格布局容器,必须和它的 "黄金搭档"GridCol(栅格子组件)一起使用。从 API version 9 开始,我们就能在开发中使用这个组件了,而且它支持手机、平板、电脑、电视甚至穿戴设备等多种终端,适用性非常广。

重点总结

  • GridRow 是栅格布局容器,仅能包含 GridCol 子组件
  • 从 API 9 开始支持,API 11 起支持元服务,API 9 起支持 ArkTS 卡片
  • 核心作用:解决多尺寸设备的动态布局问题,保证布局一致性

可能有小伙伴会问,栅格布局到底好在哪?简单说,它就像给界面画了看不见的网格线,我们的内容按照网格排列,不管屏幕多大,都能自动调整位置和大小,不用我们手动写一堆适配代码,是不是很省心?

二、怎么创建一个 GridRow?

要使用 GridRow,首先得知道它的基本构造方式。GridRow 的接口定义是这样的:

GridRow(option?: GridRowOptions)

这里的option是一个配置对象,里面包含了栅格布局的各种关键参数。我们先来看个最简单的例子,感受一下它的基本用法:

@Entry
@Component
struct GridRowBasicExample {
  build() {
    Column() {
      // 最基础的GridRow,使用默认配置
      GridRow() {
        // 放6个GridCol子组件
        GridCol() {
          Row().width('100%').height(50).backgroundColor(Color.Red)
        }
        GridCol() {
          Row().width('100%').height(50).backgroundColor(Color.Orange)
        }
        GridCol() {
          Row().width('100%').height(50).backgroundColor(Color.Yellow)
        }
        GridCol() {
          Row().width('100%').height(50).backgroundColor(Color.Green)
        }
        GridCol() {
          Row().width('100%').height(50).backgroundColor(Color.Blue)
        }
        GridCol() {
          Row().width('100%').height(50).backgroundColor(Color.Purple)
        }
      }
      .width('90%')
      .height(300)
      .borderWidth(2)
      .borderColor('#333')
    }
    .width('100%')
    .padding(10)
  }
}

重点总结

  • 创建 GridRow 只需调用GridRow()构造函数,可传入配置参数
  • 必须在内部放置 GridCol 子组件,否则无法显示内容
  • 默认情况下(API 20 前),栅格列数为 12 列,上面的例子中每个 GridCol 默认占 1 列(span 为 1)

运行这段代码,你会看到 6 个不同颜色的方块横向排列,因为默认是 12 列,每个占 1 列,所以能排下 6 个,剩下的列是空的。如果继续添加 GridCol,超过 12 个就会自动换行,这就是栅格布局的基本效果。

三、GridRow 的核心配置:GridRowOptions

GridRow 的强大之处在于它的配置选项GridRowOptions,里面包含了columns(列数)、gutter(间距)、breakpoints(断点)、direction(排列方向)四个关键参数。我们一个一个来详细说。

1. columns:控制栅格的列数

columns决定了栅格容器一共有多少列,它可以是一个数字,也可以是一个GridRowColumnOption对象(用于不同设备设置不同列数)。

重点总结

  • 取值为大于 0 的整数,或包含 xs/sm/md/lg/xl/xxl 的对象
  • API 20 之前默认值为 12,API 20 及之后默认值为{ xs: 2, sm: 4, md: 8, lg: 12, xl: 12, xxl: 12 }
  • 未明确配置的断点会自动补全,补全规则因 API 版本不同而有差异
① 用数字设置固定列数

最简单的方式是直接传一个数字,比如设置为 5 列:

GridRow({ columns: 5 }) {
  // 这里放GridCol子组件
  ForEach([1,2,3,4,5,6,7,8], (index) => {
    GridCol() {
      Row().width('100%').height(50).backgroundColor(Color.Grey)
    }
  })
}

这段代码会创建一个 5 列的栅格,每个 GridCol 占 1 列,第 6 个会自动换行,因为 5 列排满了。

② 用 GridRowColumnOption 设置自适应列数

更灵活的方式是根据不同设备宽度设置不同列数。GridRowColumnOption允许我们为 xs(最小)、sm(小)、md(中)、lg(大)、xl(特大)、xxl(超大)六种设备类型分别设置列数。

比如我们想让小屏幕设备显示 2 列,中屏幕 4 列,大屏幕 8 列,可以这样写:

GridRow({
  columns: {
    xs: 2,    // 最小屏幕:2列
    sm: 4,    // 小屏幕:4列
    md: 8,    // 中屏幕:8列
    lg: 12,   // 大屏幕:12列
    xl: 12,   // 特大屏幕:12列
    xxl: 12   // 超大屏幕:12列
  }
}) {
  // 子组件...
}

但有时候我们不想写全所有类型,这时候就会触发自动补全机制,不过 API 20 前后的补全规则不一样,这点一定要注意!

API 20 之前的补全规则
未配置的断点会取已配置的更小断点的列数,若没有更小的则用默认 12 列。

举例:

// API 19及之前
columns: { xs: 2, md: 4, lg: 8 }
// 等价于:
// { xs:2, sm:2, md:4, lg:8, xl:8, xxl:12 }
// 因为xl和xxl没有更小的配置,所以用默认12

API 20 及之后的补全规则
未配置的断点会取已配置的更小断点的列数,若没有更小的则取更大的断点列数。

举例:

// API 20及之后
columns: { md: 4, lg: 8 }
// 等价于:
// { xs:4, sm:4, md:4, lg:8, xl:8, xxl:8 }
// 因为xs和sm没有更小的配置,所以用更大的md的4列

为了避免 confusion,官方建议我们手动配置所有断点的列数。我们来写一个完整的适配代码示例:

@Entry
@Component
struct GridRowColumnsExample {
  build() {
    Column() {
      Text('不同屏幕尺寸的列数适配')
        .fontSize(16)
        .margin(10)
      
      GridRow({
        columns: {
          xs: 2,    // 手机竖屏:2列
          sm: 3,    // 手机横屏:3列
          md: 6,    // 平板竖屏:6列
          lg: 8,    // 平板横屏:8列
          xl: 12,   // 电脑小屏:12列
          xxl: 16   // 电脑大屏:16列
        },
        breakpoints: {
          value: ["320vp", "600vp", "840vp", "1080vp", "1440vp"],
          reference: BreakpointsReference.WindowSize
        }
      }) {
        // 生成20个GridCol
        ForEach(Array.from({length: 20}, (_, i) => i), (index: number) => {
          GridCol() {
            Row() {
              Text(`${index + 1}`)
                .color(Color.White)
                .fontSize(14)
            }
            .width('100%')
            .height(60)
            .backgroundColor(Color.FromArgb(200, 50, 120, 200))
          }
        })
      }
      .width('90%')
      .borderWidth(1)
      .borderColor('#eee')
    }
    .width('100%')
    .padding(10)
  }
}

代码说明

  • 我们为 6 种设备类型分别设置了列数,从 2 列到 16 列递增
  • 通过breakpoints定义了 5 个断点值(单位 vp),用于区分不同设备宽度
  • 当我们改变屏幕宽度时,列数会自动切换,比如把窗口缩小到 320vp 以下,就会显示 2 列,放大到 1440vp 以上则显示 16 列

运行这段代码,试着调整窗口大小,你会看到方块的数量会随着窗口宽度变化而变化,这就是栅格布局自适应的核心效果。

2. gutter:控制栅格之间的间距

gutter用于设置 GridCol 之间的间距,它可以是一个数字(统一间距),也可以是GutterOption对象(分别设置水平和垂直间距),甚至还能通过GridRowSizeOption为不同设备设置不同间距。

重点总结

  • 单位为 vp,默认值 0
  • 可以是数字(同时设置水平和垂直间距)
  • 可以是{x: 水平间距, y: 垂直间距}(分别设置)
  • 可以通过GridRowSizeOption为不同设备设置不同间距
① 基础用法:统一间距或分方向设置

最简单的是设置统一间距,比如gutter: 10表示水平和垂直方向的间距都是 10vp:

GridRow({
  columns: 4,
  gutter: 10  // 所有方向间距都是10vp
}) {
  // 4个GridCol...
}

如果想让水平间距和垂直间距不一样,可以用GutterOption

GridRow({
  columns: 4,
  gutter: { x: 15, y: 20 }  // 水平15vp,垂直20vp
}) {
  ForEach(Array.from({length: 8}, (_, i) => i), (index) => {
    GridCol() {
      Row().width('100%').height(50).backgroundColor(Color.LightBlue)
    }
  })
}

效果说明

  • 水平方向(x):每个 GridCol 之间的间距是 15vp
  • 垂直方向(y):换行后的行与行之间的间距是 20vp
② 进阶用法:不同设备设置不同间距

有时候我们希望小屏幕设备间距小一点,大屏幕间距大一点,这时候可以用GridRowSizeOption

GridRow({
  columns: { xs: 2, md: 4, lg: 6 },
  gutter: {
    x: {
      xs: 5,   // 最小屏幕:水平间距5vp
      sm: 8,   // 小屏幕:水平间距8vp
      md: 12,  // 中屏幕:水平间距12vp
      lg: 15   // 大屏幕及以上:水平间距15vp
    },
    y: {
      xs: 8,
      md: 15,
      lg: 20
    }
  }
}) {
  // 子组件...
}

代码说明

  • x表示水平方向间距,为不同设备设置了 5vp 到 15vp 的间距
  • y表示垂直方向间距,从 8vp 到 20vp 递增
  • 未配置的断点(如 xl、xxl)会根据 API 版本自动补全,建议手动配置

我们来写一个完整的示例,展示不同设备的间距变化:

@Entry
@Component
struct GridRowGutterExample {
  build() {
    Column() {
      Text('不同设备的间距适配')
        .fontSize(16)
        .margin(10)
      
      GridRow({
        columns: { xs: 2, sm: 3, md: 4, lg: 6 },
        gutter: {
          x: {
            xs: 5,
            sm: 8,
            md: 10,
            lg: 15
          },
          y: {
            xs: 8,
            sm: 10,
            md: 15,
            lg: 20
          }
        },
        breakpoints: {
          value: ["320vp", "600vp", "840vp", "1200vp"]
        }
      }) {
        ForEach(Array.from({length: 12}, (_, i) => i), (index: number) => {
          GridCol() {
            Row()
              .width('100%')
              .height(50)
              .backgroundColor(Color.FromArgb(200, 200, 80, 100))
          }
        })
      }
      .width('90%')
      .borderWidth(1)
      .borderColor('#f0f0f0')
      .padding(5)  // 容器内边距
    }
    .width('100%')
    .padding(10)
  }
}

效果说明

  • 当屏幕宽度小于 320vp(xs)时,水平间距 5vp,垂直间距 8vp
  • 当宽度在 600vp-840vp(md)时,水平间距 10vp,垂直间距 15vp
  • 屏幕越大,间距也越大,让布局在大屏幕上更舒展

这里要注意一个计算细节:每列的宽度是怎么来的?官方给出了公式:

每列宽度 = (GridRow 内容区宽度 - 左右内边距2 - 水平间距(列数 - 1)) / 列数

比如一个宽 800vp 的 GridRow,设置了 columns=12,gutter.x=10vp,padding=20vp,那么:

每列宽度 = (800 - 202 - 1011) / 12 = (800 - 40 - 110) / 12 = 650 / 12 ≈ 54.17vp

了解这个公式能帮我们更好地预估布局效果,避免出现元素溢出或间距不协调的问题。

3. breakpoints:定义断点值和参照标准

breakpoints用于设置栅格布局的断点值,也就是不同设备宽度的分界线,它决定了什么时候切换到 xs、sm、md 等设备类型。

重点总结

  • 默认值:{ value: ["320vp", "600vp", "840vp"], reference: BreakpointsReference.WindowSize }
  • value是单调递增的断点数组(单位 vp),数量决定了设备类型数量(n 个断点对应 n+1 种类型)
  • reference可选WindowSize(窗口宽度)或ComponentSize(GridRow 自身宽度)
断点与设备类型的对应关系

断点数组value的长度决定了设备类型的数量,比如:

  • 3 个断点值 → 4 个区间 → 对应 xs、sm、md、lg 四种类型
  • 5 个断点值 → 6 个区间 → 对应 xs、sm、md、lg、xl、xxl 六种类型

具体对应关系如下(以默认 3 个断点为例):

  • xs:小于第一个断点(<320vp)
  • sm:第一个到第二个断点(320vp ≤ 宽度 <600vp)
  • md:第二个到第三个断点(600vp ≤ 宽度 <840vp)
  • lg:大于等于第三个断点(≥840vp)

如果我们自定义 5 个断点["320vp", "600vp", "840vp", "1080vp", "1440vp"],则对应:

  • xs:<320vp
  • sm:320vp-600vp
  • md:600vp-840vp
  • lg:840vp-1080vp
  • xl:1080vp-1440vp
  • xxl:≥1440vp
实战示例:自定义断点

我们来写一个自定义断点的例子,让断点更符合实际设备尺寸:

@Entry
@Component
struct GridRowBreakpointsExample {
  @State currentBp: string = 'unknown'  // 用于显示当前断点类型

  build() {
    Column() {
      Text(`当前断点类型:${this.currentBp}`)
        .fontSize(16)
        .margin(10)
      
      GridRow({
        columns: {
          xs: 1,
          sm: 2,
          md: 4,
          lg: 6,
          xl: 8,
          xxl: 12
        },
        breakpoints: {
          value: ["360vp", "720vp", "1080vp", "1440vp", "1920vp"],  // 5个断点
          reference: BreakpointsReference.WindowSize  // 以窗口宽度为参照
        }
      }) {
        ForEach(Array.from({length: 12}, (_, i) => i), (index: number) => {
          GridCol() {
            Row()
              .width('100%')
              .height(50)
              .backgroundColor(Color.FromArgb(180, 80, 180, 100))
          }
        })
      }
      .width('90%')
      .onBreakpointChange((bp) => {
        this.currentBp = bp  // 监听断点变化,更新显示
      })
      .borderWidth(1)
      .borderColor('#eee')
    }
    .width('100%')
    .padding(10)
  }
}

代码说明

  • 我们设置了 5 个断点值,对应 6 种设备类型(xs 到 xxl)
  • 通过onBreakpointChange事件监听断点变化,实时显示当前类型
  • 当窗口宽度跨越断点值时(比如从 359vp 增大到 360vp),会触发事件,列数也会随之改变
以组件自身宽度为参照

默认referenceWindowSize(窗口宽度),如果设置为ComponentSize,则以 GridRow 自身的宽度为参照,这在 GridRow 不是全屏显示时很有用:

GridRow({
  breakpoints: {
    value: ["200vp", "400vp", "600vp"],
    reference: BreakpointsReference.ComponentSize  // 以GridRow自身宽度为参照
  }
}) {
  // 子组件...
}
.width('50%')  // GridRow宽度为窗口的50%

这时,断点判断的是 GridRow 自身的宽度(比如 200vp 是指 GridRow 的宽度,而不是窗口宽度),适合局部区域的自适应布局。

4. direction:设置排列方向

direction用于设置 GridCol 的排列方向,只有两个可选值:

重点总结

  • 默认值:GridRowDirection.Row(从左到右排列)
  • 可选值:Row(正向)或RowReverse(反向)
  • 不支持垂直方向(Column/ColumnReverse)排列

举例说明两种方向的区别:

@Entry
@Component
struct GridRowDirectionExample {
  build() {
    Column() {
      Text('Row方向(默认)')
        .fontSize(14)
        .margin(5)
      
      GridRow({
        columns: 4,
        gutter: 5,
        direction: GridRowDirection.Row
      }) {
        this.createGridCols()
      }
      .width('90%')
      .height(100)
      .borderWidth(1)
      
      Text('RowReverse方向(反向)')
        .fontSize(14)
        .margin({ top: 20, bottom: 5 })
      
      GridRow({
        columns: 4,
        gutter: 5,
        direction: GridRowDirection.RowReverse
      }) {
        this.createGridCols()
      }
      .width('90%')
      .height(100)
      .borderWidth(1)
    }
    .width('100%')
    .padding(10)
  }
  
  // 创建4个带编号的GridCol
  createGridCols() {
    ForEach(Array.from({length: 4}, (_, i) => i), (index: number) => {
      GridCol() {
        Row() {
          Text(`${index + 1}`)
            .color(Color.White)
        }
        .width('100%')
        .height('100%')
        .backgroundColor(Color.FromArgb(200, 100, 80, 200))
      }
    })
  }
}

效果说明

  • 第一个 GridRow(Row 方向):数字 1-4 从左到右排列
  • 第二个 GridRow(RowReverse 方向):数字 1-4 从右到左排列(显示为 4、3、2、1)

需要注意的是,不管是正向还是反向,当子组件总列数超过columns时都会自动换行,只是每行的排列顺序相反。

四、GridCol 的配合使用

GridRow 必须和 GridCol 配合使用,GridCol 通过span(占列数)、offset(偏移量)、order(排序)等参数控制自身在栅格中的位置和大小。虽然本文重点是 GridRow,但了解 GridCol 的基本用法能帮我们更好地掌握整体布局。

GridCol 的常用配置

GridCol({
  span: 4,        // 占4列(可设置不同断点的span,如{xs:2, md:4})
  offset: 2,      // 向右偏移2列(可选)
  order: 1        // 排序权重(数字越小越靠前,可选)
}) {
  // 内容组件...
}

我们来写一个 GridRow 与 GridCol 配合的完整示例:

@Entry
@Component
struct GridRowWithColExample {
  build() {
    Column() {
      Text('GridRow与GridCol配合使用')
        .fontSize(16)
        .margin(10)
      
      GridRow({
        columns: 12,
        gutter: { x: 8, y: 10 }
      }) {
        // 占6列(一半宽度)
        GridCol({ span: 6 }) {
          Row()
            .width('100%')
            .height(80)
            .backgroundColor(Color.Red)
        }
        
        // 占3列,偏移3列(右边空3列)
        GridCol({ span: 3, offset: 3 }) {
          Row()
            .width('100%')
            .height(80)
            .backgroundColor(Color.Green)
        }
        
        // 第二行:占4列
        GridCol({ span: 4 }) {
          Row()
            .width('100%')
            .height(80)
            .backgroundColor(Color.Blue)
        }
        
        // 占8列(4+8=12,刚好一行)
        GridCol({ span: 8 }) {
          Row()
            .width('100%')
            .height(80)
            .backgroundColor(Color.Yellow)
        }
        
        // 跨两行的效果(通过高度实现)
        GridCol({ span: 12 }) {
          Row()
            .width('100%')
            .height(120)
            .backgroundColor(Color.Purple)
        }
      }
      .width('90%')
      .borderWidth(1)
      .borderColor('#eee')
    }
    .width('100%')
    .padding(10)
  }
}

代码说明

  • 第一个 GridCol 占 6 列(12 列的一半),显示红色块
  • 第二个 GridCol 占 3 列,偏移 3 列(前面空 3 列),所以显示在右边 3 列,绿色块
  • 第三、四个 GridCol 分别占 4 列和 8 列,加起来 12 列,组成第二行
  • 最后一个 GridCol 占 12 列(整行),高度 120vp,跨两行的视觉效果

运行这段代码,你会看到一个包含不同大小和位置的色块布局,展示了栅格布局的灵活性。

五、GridRow 的属性和事件

除了构造函数中的配置,GridRow 还有一些常用的属性和事件,帮助我们进一步控制布局效果。

1. alignItems:设置垂直对齐方式

从 API 10 开始,GridRow 支持alignItems属性,用于设置所有 GridCol 在垂直方向(主轴为水平方向时,垂直为交叉轴)的对齐方式。

重点总结

  • 可选值:ItemAlign.Start(顶部)、Center(居中)、End(底部)、Stretch(拉伸)
  • 默认值:ItemAlign.Start
  • GridCol 的alignSelf属性可以覆盖 GridRow 的alignItems设置

示例代码:

@Entry
@Component
struct GridRowAlignItemsExample {
  build() {
    Column() {
      // 顶部对齐(默认)
      GridRow({ columns: 3, gutter: 8 }) {
        this.createCol(100)  // 高度100vp
        this.createCol(60)   // 高度60vp
        this.createCol(80)   // 高度80vp
      }
      .alignItems(ItemAlign.Start)
      .width('90%')
      .height(150)
      .backgroundColor('#f5f5f5')
      .marginBottom(20)
      
      // 居中对齐
      GridRow({ columns: 3, gutter: 8 }) {
        this.createCol(100)
        this.createCol(60)
        this.createCol(80)
      }
      .alignItems(ItemAlign.Center)
      .width('90%')
      .height(150)
      .backgroundColor('#f5f5f5')
      .marginBottom(20)
      
      // 底部对齐
      GridRow({ columns: 3, gutter: 8 }) {
        this.createCol(100)
        this.createCol(60)
        this.createCol(80)
      }
      .alignItems(ItemAlign.End)
      .width('90%')
      .height(150)
      .backgroundColor('#f5f5f5')
    }
    .width('100%')
    .padding(10)
  }
  
  // 创建指定高度的GridCol
  createCol(height: number) {
    return GridCol() {
      Row()
        .width('100%')
        .height(height)
        .backgroundColor(Color.FromArgb(200, 120, 80, 180))
    }
  }
}

效果说明

  • 第一个 GridRow:三个色块顶部对齐,底部有空白
  • 第二个 GridRow:三个色块垂直居中对齐
  • 第三个 GridRow:三个色块底部对齐,顶部有空白

如果想单独设置某个 GridCol 的对齐方式,可以用alignSelf

GridCol() {
  // 内容...
}
.alignSelf(ItemAlign.Center)  // 覆盖GridRow的alignItems

2. onBreakpointChange:监听断点变化

这个事件我们在前面的例子中已经用过了,它会在设备类型(xs、sm 等)发生变化时触发,回调参数是当前的断点类型字符串。

实用场景

  • 记录当前设备类型,用于后续业务逻辑
  • 根据设备类型动态修改组件样式或数据
  • 统计不同设备的使用比例

示例代码:

@Entry
@Component
struct GridRowBreakpointEventExample {
  @State deviceType: string = 'xs'
  @State fontSize: number = 14

  build() {
    Column() {
      Text(`当前设备:${this.deviceType},字体大小:${this.fontSize}px`)
        .fontSize(this.fontSize)
        .margin(10)
      
      GridRow({
        columns: { xs: 2, sm: 3, md: 4, lg: 6 },
        breakpoints: { value: ["320vp", "600vp", "840vp"] }
      }) {
        ForEach(Array.from({length: 8}, (_, i) => i), (index) => {
          GridCol() {
            Row()
              .width('100%')
              .height(50)
              .backgroundColor(Color.FromArgb(180, 80, 120, 200))
          }
        })
      }
      .width('90%')
      .onBreakpointChange((bp) => {
        this.deviceType = bp
        // 根据设备类型调整字体大小
        switch(bp) {
          case 'xs':
            this.fontSize = 14
            break
          case 'sm':
            this.fontSize = 16
            break
          case 'md':
            this.fontSize = 18
            break
          case 'lg':
            this.fontSize = 20
            break
        }
      })
    }
    .width('100%')
    .padding(10)
  }
}

代码说明

  • 监听断点变化,更新deviceType显示当前设备类型
  • 根据不同设备类型动态调整字体大小(xs→14,lg→20)
  • 这种动态调整能让界面在不同设备上都保持良好的可读性

六、实战综合案例:多设备自适应布局页面

学到这里,我们已经掌握了 GridRow 的所有核心用法,现在来做一个综合案例:一个包含头部、内容区、侧边栏和底部的页面,在不同设备上有不同的布局效果。

需求说明

  • 手机(xs/sm):垂直布局,头部→内容区→侧边栏→底部
  • 平板(md):头部→内容区(左 60%)+ 侧边栏(右 40%)→ 底部
  • 电脑(lg 及以上):头部→内容区(左 70%)+ 侧边栏(右 30%)→ 底部

实现代码:

@Entry
@Component
struct GridRowComprehensiveExample {
  @State currentBp: string = 'xs'

  build() {
    Column() {
      // 页面标题和当前设备类型
      Row() {
        Text('多设备自适应布局示例')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        Text(`[${this.currentBp}]`)
          .fontSize(14)
          .marginLeft(10)
          .backgroundColor(Color.LightGray)
          .padding({ left: 5, right: 5 })
      }
      .margin(10)
      .width('90%')
      
      // 栅格布局容器
      GridRow({
        columns: {
          xs: 12,  // 手机:12列
          sm: 12,
          md: 12,  // 平板:12列
          lg: 12   // 电脑:12列
        },
        gutter: { x: 10, y: 15 },
        breakpoints: {
          value: ["320vp", "600vp", "840vp"],
          reference: BreakpointsReference.WindowSize
        }
      }) {
        // 头部(占12列,所有设备都全屏)
        GridCol({ span: 12 }) {
          Row() {
            Text('页面头部')
              .color(Color.White)
              .fontSize(16)
          }
          .width('100%')
          .height(60)
          .backgroundColor(Color.FromArgb(200, 50, 100, 200))
          .padding({ left: 15 })
        }
        
        // 内容区(手机占12列,平板占7列,电脑占8列)
        GridCol({
          span: {
            xs: 12,
            sm: 12,
            md: 7,   // 平板:7/12
            lg: 8    // 电脑:8/12
          }
        }) {
          Column() {
            Text('主要内容区')
              .fontSize(16)
              .color(Color.White)
              .margin(10)
            
            // 模拟内容
            ForEach(Array.from({length: 3}, (_, i) => i), (index) => {
              Row()
                .width('90%')
                .height(80)
                .backgroundColor(Color.White)
                .margin(5)
                .borderRadius(4)
            })
          }
          .width('100%')
          .backgroundColor(Color.FromArgb(200, 200, 200, 200))
          .borderRadius(4)
        }
        
        // 侧边栏(手机占12列,平板占5列,电脑占4列)
        GridCol({
          span: {
            xs: 12,
            sm: 12,
            md: 5,   // 平板:5/12(7+5=12)
            lg: 4    // 电脑:4/12(8+4=12)
          }
        }) {
          Column() {
            Text('侧边栏')
              .fontSize(16)
              .color(Color.White)
              .margin(10)
            
            // 模拟侧边栏内容
            Row()
              .width('90%')
              .height(120)
              .backgroundColor(Color.White)
              .margin(5)
              .borderRadius(4)
          }
          .width('100%')
          .backgroundColor(Color.FromArgb(200, 200, 100, 100))
          .borderRadius(4)
        }
        
        // 底部(占12列,所有设备都全屏)
        GridCol({ span: 12 }) {
          Row() {
            Text('页面底部')
              .color(Color.White)
              .fontSize(14)
          }
          .width('100%')
          .height(50)
          .backgroundColor(Color.FromArgb(200, 80, 80, 80))
          .padding({ left: 15 })
          .justifyContent(FlexAlign.Center)
        }
      }
      .width('90%')
      .onBreakpointChange((bp) => {
        this.currentBp = bp  // 更新当前设备类型
      })
    }
    .width('100%')
    .padding(10)
    .backgroundColor('#f9f9f9')
  }
}

代码说明

  • 我们通过 GridCol 的span属性,为不同设备设置了不同的占列数:
    • 手机(xs/sm):内容区和侧边栏都占 12 列,所以垂直排列
    • 平板(md):内容区 7 列,侧边栏 5 列(7+5=12),水平排列
    • 电脑(lg):内容区 8 列,侧边栏 4 列(8+4=12),水平排列
  • 通过gutter设置了水平 10vp、垂直 15vp 的间距,让区域之间有适当空隙
  • 头部和底部在所有设备上都占 12 列,保持全屏显示

运行这段代码,调整窗口大小,你会看到:

  • 小窗口(手机):头部→内容区→侧边栏→底部,垂直排列
  • 中窗口(平板):头部→(内容区 + 侧边栏)→底部,内容区占大部分宽度
  • 大窗口(电脑):头部→(内容区 + 侧边栏)→底部,内容区更宽,侧边栏更窄

这个例子完美展示了 GridRow 在多设备自适应布局中的强大能力,通过简单的配置就能实现复杂的布局适配,大大减少了我们编写适配代码的工作量。

七、常见问题与最佳实践

掌握了 GridRow 的基本用法后,我们再来看看实际开发中可能遇到的问题和一些最佳实践建议。

1. 为什么列数设置不生效?

可能的原因:

  • 列数设置为 0 或负数(必须大于 0)
  • API 版本差异导致补全规则不同(特别是 API 20 前后)
  • 断点设置错误,导致设备类型判断不准确
  • GridRow 的宽度为 0(比如没设置 width,父组件限制了宽度)

解决办法:

  • 确保 columns 取值大于 0
  • 明确设置所有断点的列数,避免依赖自动补全
  • 通过onBreakpointChange确认当前设备类型是否正确
  • 检查 GridRow 的宽度是否正确设置(建议用百分比或固定值)

2. 为什么 GridCol 会溢出或换行异常?

可能的原因:

  • span+offset的总和超过了总列数
  • gutter 间距过大,导致内容区被压缩
  • GridCol 内部组件设置了固定宽度,超过了栅格列宽

解决办法:

  • 确保span + offset ≤ columns
  • 减小 gutter 间距,或增大 GridRow 宽度
  • 内部组件宽度尽量用100%,避免固定值
  • 用前面提到的列宽计算公式预估布局效果

3. 最佳实践建议

  • 明确配置所有断点:虽然有自动补全机制,但手动配置所有断点的列数和间距能避免意外效果
  • 优先使用相对单位:宽度尽量用百分比,间距和高度用 vp,确保在不同分辨率设备上显示一致
  • 合理设置断点值:参考实际设备的屏幕尺寸设置断点(如手机竖屏、横屏,平板的不同方向等)
  • 利用 onBreakpointChange 做精细化控制:对于复杂场景,可在断点变化时动态修改数据或样式
  • 测试多种设备尺寸:开发过程中多调整窗口大小,检查不同尺寸下的布局效果

八、总结

GridRow 作为 HarmonyOS 提供的栅格布局容器,是解决多设备自适应布局问题的利器。通过与 GridCol 配合使用,我们可以轻松实现各种复杂的布局,并保证在手机、平板、电脑等不同设备上都有良好的显示效果。

本文从 GridRow 的基本概念、核心配置、属性事件、实战案例等多个方面进行了详细讲解,涵盖了从入门到精通的所有知识点。重点包括:

  • GridRow 与 GridCol 的配合使用
  • columns、gutter、breakpoints、direction 四大核心配置
  • 不同 API 版本的差异(特别是 API 20 前后的列数补全规则)
  • 多设备自适应布局的实现方法
  • 常见问题的解决办法

掌握 GridRow 不仅能提高我们的布局效率,还能让界面在各种设备上保持一致的用户体验。希望本文的内容能帮助你在 HarmonyOS 应用开发中更好地运用栅格布局,开发出更优秀的应用!

Logo

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

更多推荐