在这里插入图片描述

概述

网格布局是现代 UI 设计中常用的布局方式,能够将内容以行列形式整齐排列。HarmonyOS ArkUI 提供的 Grid 组件功能强大,支持灵活的列数配置、间距设置、自适应布局等特性。本文将从组件基础、属性配置、样式定制、响应式布局、实际应用等多个维度,深入解析 Grid 组件的使用方法。


一、Grid 组件基础

1.1 组件定义与作用

Grid 组件用于创建网格布局,将子组件排列成多行多列的网格结构。它是构建图片墙、商品列表、应用图标等场景的核心组件。

@Entry
@Component
struct GridBasic {
  build() {
    Column() {
      Grid() {
        ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .fontSize(16)
              .padding(16)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .width('100%')
    }
    .padding(20)
  }
}

1.2 核心属性

属性 类型 说明 默认值
columnsTemplate string 列模板 -
rowsTemplate string 行模板 -
columnsGap Length 列间距 0
rowsGap Length 行间距 0
width Length 网格宽度 -
height Length 网格高度 -
layoutDirection LayoutDirection 布局方向 LayoutDirection.Ltr

1.3 列模板语法

columnsTemplate 使用空格分隔的模板字符串,支持以下单位:

单位 说明 示例
fr 剩余空间分配 '1fr 1fr 1fr'
px 固定像素 '100px 100px'
% 百分比 '50% 50%'

1.4 基础使用示例

@Entry
@Component
struct BasicGrid {
  build() {
    Column() {
      Text('基础网格布局')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 16 })
      
      Grid() {
        ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .fontSize(14)
              .padding(16)
              .backgroundColor('#FFFFFF')
              .borderRadius(8)
              .width('100%')
              .textAlign(TextAlign.Center)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(10)
      .rowsGap(10)
      .width('100%')
      .backgroundColor('#F5F5F5')
      .padding(10)
    }
    .padding(20)
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}

二、属性配置

2.1 列数配置

通过 columnsTemplate 配置列数和宽度:

@Entry
@Component
struct ColumnConfig {
  build() {
    Column() {
      // 2列等宽
      Grid() {
        ForEach([1, 2, 3, 4], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .padding(16)
              .backgroundColor('#FFFFFF')
          }
        })
      }
      .columnsTemplate('1fr 1fr')
      .width('100%')
      .margin({ bottom: 16 })
      
      // 3列等宽
      Grid() {
        ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .padding(16)
              .backgroundColor('#FFFFFF')
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .width('100%')
      
      // 4列等宽
      Grid() {
        ForEach([1, 2, 3, 4, 5, 6, 7, 8], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .padding(16)
              .backgroundColor('#FFFFFF')
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr 1fr')
      .width('100%')
      .margin({ top: 16 })
    }
    .padding(20)
  }
}

2.2 不等宽布局

创建不同宽度的列:

@Entry
@Component
struct UnequalGrid {
  build() {
    Column() {
      Grid() {
        ForEach([1, 2, 3, 4], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .padding(16)
              .backgroundColor('#FFFFFF')
              .textAlign(TextAlign.Center)
          }
        })
      }
      .columnsTemplate('1fr 2fr')
      .columnsGap(10)
      .rowsGap(10)
      .width('100%')
    }
    .padding(20)
  }
}

2.3 固定宽度布局

使用固定像素宽度:

@Entry
@Component
struct FixedWidthGrid {
  build() {
    Column() {
      Grid() {
        ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .padding(16)
              .backgroundColor('#FFFFFF')
              .textAlign(TextAlign.Center)
          }
        })
      }
      .columnsTemplate('80px 80px 80px')
      .columnsGap(10)
      .rowsGap(10)
      .width('100%')
    }
    .padding(20)
  }
}

2.4 间距设置

通过 columnsGaprowsGap 设置间距:

@Entry
@Component
struct GapGrid {
  build() {
    Column() {
      Grid() {
        ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .padding(16)
              .backgroundColor('#FFFFFF')
              .textAlign(TextAlign.Center)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(16)
      .rowsGap(16)
      .width('100%')
    }
    .padding(20)
  }
}

三、样式定制

3.1 背景与边框

为网格添加背景色和边框:

@Entry
@Component
struct BackgroundGrid {
  build() {
    Column() {
      Grid() {
        ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .padding(16)
              .backgroundColor('#FFFFFF')
              .textAlign(TextAlign.Center)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(10)
      .rowsGap(10)
      .width('100%')
      .backgroundColor('#F5F5F5')
      .border({ width: 1, color: '#E5E5E5', radius: 8 })
      .padding(10)
    }
    .padding(20)
  }
}

3.2 网格项样式

定制网格项的外观:

@Entry
@Component
struct StyledGridItem {
  build() {
    Column() {
      Grid() {
        ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .padding(16)
              .backgroundColor('#0A59F7')
              .fontColor('#FFFFFF')
              .fontSize(14)
              .fontWeight(FontWeight.Medium)
              .textAlign(TextAlign.Center)
              .borderRadius(8)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(10)
      .rowsGap(10)
      .width('100%')
    }
    .padding(20)
  }
}

3.3 圆角与阴影

添加圆角和阴影效果:

@Entry
@Component
struct RoundedGrid {
  build() {
    Column() {
      Grid() {
        ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .padding(16)
              .backgroundColor('#FFFFFF')
              .textAlign(TextAlign.Center)
              .borderRadius(12)
              .shadow({ radius: 4, color: 'rgba(0, 0, 0, 0.1)' })
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(12)
      .rowsGap(12)
      .width('100%')
    }
    .padding(20)
  }
}

3.4 完整样式示例

@Entry
@Component
struct CompleteGridStyle {
  @State colors: string[] = ['#0A59F7', '#34C759', '#FF9500', '#AF52DE', '#FF2D55', '#5856D6'];

  build() {
    Column() {
      Text('多彩网格')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 16 })
      
      Grid() {
        ForEach([1, 2, 3, 4, 5, 6], (item: number, index: number) => {
          GridItem() {
            Text('项目' + item)
              .padding(20)
              .backgroundColor(this.colors[index])
              .fontColor('#FFFFFF')
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .textAlign(TextAlign.Center)
              .borderRadius(12)
          }
        })
      }
      .columnsTemplate('1fr 1fr')
      .columnsGap(12)
      .rowsGap(12)
      .width('100%')
      .backgroundColor('#F5F5F5')
      .padding(12)
      .borderRadius(12)
    }
    .padding(20)
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}

四、响应式布局

4.1 动态列数

根据状态变量动态调整列数:

@Entry
@Component
struct DynamicColumns {
  @State columns: string = '1fr 1fr 1fr';

  build() {
    Column() {
      Row() {
        Button('2列')
          .layoutWeight(1)
          .height(40)
          .onClick(() => {
            this.columns = '1fr 1fr';
          })
        Button('3列')
          .layoutWeight(1)
          .height(40)
          .margin({ left: 8 })
          .onClick(() => {
            this.columns = '1fr 1fr 1fr';
          })
        Button('4列')
          .layoutWeight(1)
          .height(40)
          .margin({ left: 8 })
          .onClick(() => {
            this.columns = '1fr 1fr 1fr 1fr';
          })
      }
      .width('100%')
      .margin({ bottom: 16 })
      
      Grid() {
        ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .padding(20)
              .backgroundColor('#0A59F7')
              .fontColor('#FFFFFF')
              .textAlign(TextAlign.Center)
              .borderRadius(8)
          }
        })
      }
      .columnsTemplate(this.columns)
      .columnsGap(8)
      .rowsGap(8)
      .width('100%')
    }
    .padding(20)
  }
}

4.2 结合媒体查询

根据屏幕尺寸调整布局:

@Entry
@Component
struct ResponsiveGrid {
  @State columns: string = '1fr 1fr';

  aboutToAppear() {
    const width: number = px2vp(windowWidth());
    if (width > 600) {
      this.columns = '1fr 1fr 1fr';
    }
    if (width > 800) {
      this.columns = '1fr 1fr 1fr 1fr';
    }
  }

  build() {
    Column() {
      Grid() {
        ForEach([1, 2, 3, 4, 5, 6, 7, 8], (item: number) => {
          GridItem() {
            Text('格子' + item)
              .padding(20)
              .backgroundColor('#FFFFFF')
              .textAlign(TextAlign.Center)
              .borderRadius(8)
          }
        })
      }
      .columnsTemplate(this.columns)
      .columnsGap(10)
      .rowsGap(10)
      .width('100%')
    }
    .padding(20)
  }
}

五、高级用法

5.1 带图标的网格

创建包含图标的网格布局:

@Entry
@Component
struct IconGrid {
  @State menuItems: { icon: string; title: string; color: string }[] = [
    { icon: '📱', title: '手机', color: '#0A59F7' },
    { icon: '📧', title: '邮件', color: '#34C759' },
    { icon: '📷', title: '相机', color: '#FF9500' },
    { icon: '🎵', title: '音乐', color: '#AF52DE' },
    { icon: '📁', title: '文件', color: '#FF2D55' },
    { icon: '⚙️', title: '设置', color: '#5856D6' }
  ];

  build() {
    Column() {
      Grid() {
        ForEach(this.menuItems, (item) => {
          GridItem() {
            Column() {
              Text(item.icon)
                .fontSize(32)
              Text(item.title)
                .fontSize(14)
                .fontColor('#333333')
                .margin({ top: 8 })
            }
            .padding(16)
            .backgroundColor('#FFFFFF')
            .borderRadius(12)
            .width('100%')
            .alignItems(HorizontalAlign.Center)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(12)
      .rowsGap(12)
      .width('100%')
    }
    .padding(20)
  }
}

5.2 图片网格

创建图片墙效果:

@Entry
@Component
struct ImageGrid {
  @State images: string[] = [
    'https://example.com/img1.jpg',
    'https://example.com/img2.jpg',
    'https://example.com/img3.jpg',
    'https://example.com/img4.jpg',
    'https://example.com/img5.jpg',
    'https://example.com/img6.jpg'
  ];

  build() {
    Column() {
      Grid() {
        ForEach(this.images, (url: string) => {
          GridItem() {
            Image(url)
              .width('100%')
              .height(120)
              .objectFit(ImageFit.Cover)
              .borderRadius(8)
          }
        })
      }
      .columnsTemplate('1fr 1fr')
      .columnsGap(8)
      .rowsGap(8)
      .width('100%')
    }
    .padding(20)
  }
}

5.3 网格点击事件

为网格项添加点击事件:

@Entry
@Component
struct ClickableGrid {
  @State items: string[] = ['项目1', '项目2', '项目3', '项目4', '项目5', '项目6'];
  @State selectedItem: string = '';

  build() {
    Column() {
      Text('选中:' + (this.selectedItem || '无'))
        .fontSize(14)
        .fontColor('#0A59F7')
        .margin({ bottom: 16 })
      
      Grid() {
        ForEach(this.items, (item: string) => {
          GridItem() {
            Text(item)
              .padding(20)
              .backgroundColor(this.selectedItem === item ? '#0A59F7' : '#FFFFFF')
              .fontColor(this.selectedItem === item ? '#FFFFFF' : '#333333')
              .textAlign(TextAlign.Center)
              .borderRadius(8)
          }
          .onClick(() => {
            this.selectedItem = item;
          })
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(10)
      .rowsGap(10)
      .width('100%')
    }
    .padding(20)
  }
}

5.4 嵌套网格

在网格中嵌套其他布局:

@Entry
@Component
struct NestedGrid {
  build() {
    Column() {
      Grid() {
        GridItem() {
          Column() {
            Text('标题')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
            Text('描述信息')
              .fontSize(14)
              .fontColor('#999999')
              .margin({ top: 4 })
          }
          .padding(16)
          .backgroundColor('#FFFFFF')
          .borderRadius(8)
        }
        
        GridItem() {
          Column() {
            Text('标题')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
            Text('描述信息')
              .fontSize(14)
              .fontColor('#999999')
              .margin({ top: 4 })
          }
          .padding(16)
          .backgroundColor('#FFFFFF')
          .borderRadius(8)
        }
        
        GridItem() {
          Column() {
            Text('标题')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
            Text('描述信息')
              .fontSize(14)
              .fontColor('#999999')
              .margin({ top: 4 })
          }
          .padding(16)
          .backgroundColor('#FFFFFF')
          .borderRadius(8)
        }
        
        GridItem() {
          Column() {
            Text('标题')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
            Text('描述信息')
              .fontSize(14)
              .fontColor('#999999')
              .margin({ top: 4 })
          }
          .padding(16)
          .backgroundColor('#FFFFFF')
          .borderRadius(8)
        }
      }
      .columnsTemplate('1fr 1fr')
      .columnsGap(10)
      .rowsGap(10)
      .width('100%')
    }
    .padding(20)
  }
}

六、实际案例:应用商店首页

6.1 需求分析

构建一个应用商店首页,包含:

  • 应用分类网格
  • 热门应用列表
  • 推荐应用网格
  • 应用图标和名称展示

6.2 代码实现

import { router } from '@kit.ArkUI';

class AppInfo {
  id: number = 0;
  name: string = '';
  icon: string = '';
  rating: number = 0;
  downloads: string = '';

  constructor(id: number, name: string, icon: string, rating: number, downloads: string) {
    this.id = id;
    this.name = name;
    this.icon = icon;
    this.rating = rating;
    this.downloads = downloads;
  }
}

class Category {
  name: string = '';
  icon: string = '';
  color: string = '';

  constructor(name: string, icon: string, color: string) {
    this.name = name;
    this.icon = icon;
    this.color = color;
  }
}

@Entry
@Component
struct AppStore {
  @State categories: Category[] = [
    new Category('游戏', '🎮', '#FF3B30'),
    new Category('社交', '💬', '#0A59F7'),
    new Category('工具', '🔧', '#34C759'),
    new Category('娱乐', '🎵', '#FF9500'),
    new Category('购物', '🛒', '#AF52DE'),
    new Category('教育', '📚', '#5856D6')
  ];
  
  @State hotApps: AppInfo[] = [
    new AppInfo(1, '微信', '💬', 4.9, '10亿+'),
    new AppInfo(2, '抖音', '🎵', 4.8, '8亿+'),
    new AppInfo(3, '支付宝', '💰', 4.7, '7亿+'),
    new AppInfo(4, '淘宝', '🛒', 4.6, '6亿+')
  ];
  
  @State recommendApps: AppInfo[] = [
    new AppInfo(5, '王者荣耀', '🎮', 4.9, '2亿+'),
    new AppInfo(6, '小红书', '📕', 4.8, '1亿+'),
    new AppInfo(7, '美团', '🍔', 4.7, '5亿+'),
    new AppInfo(8, '高德地图', '🗺️', 4.6, '4亿+'),
    new AppInfo(9, '哔哩哔哩', '📺', 4.8, '3亿+'),
    new AppInfo(10, '酷狗音乐', '🎶', 4.5, '2亿+')
  ];

  build() {
    Column() {
      // 顶部标题
      Text('应用商店')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 40, bottom: 24 })
      
      // 分类网格
      Column() {
        Text('分类')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .margin({ bottom: 12 })
        
        Grid() {
          ForEach(this.categories, (category: Category) => {
            GridItem() {
              Column() {
                Text(category.icon)
                  .fontSize(28)
                Text(category.name)
                  .fontSize(13)
                  .fontColor('#333333')
                  .margin({ top: 6 })
              }
              .padding({ top: 16, bottom: 16 })
              .backgroundColor('#FFFFFF')
              .borderRadius(12)
              .width('100%')
              .alignItems(HorizontalAlign.Center)
            }
          })
        }
        .columnsTemplate('1fr 1fr 1fr')
        .columnsGap(10)
        .rowsGap(10)
        .width('100%')
      }
      .margin({ bottom: 24 })
      
      // 热门应用
      Column() {
        Text('热门应用')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .margin({ bottom: 12 })
        
        List() {
          ForEach(this.hotApps, (app: AppInfo) => {
            ListItem() {
              Row() {
                Text(app.icon)
                  .fontSize(40)
                Column() {
                  Text(app.name)
                    .fontSize(16)
                    .fontWeight(FontWeight.Medium)
                  Text(app.downloads + '下载')
                    .fontSize(12)
                    .fontColor('#999999')
                    .margin({ top: 4 })
                }
                .layoutWeight(1)
                .margin({ left: 12 })
                .alignItems(HorizontalAlign.Start)
                Text(app.rating + '分')
                  .fontSize(14)
                  .fontColor('#FF9500')
              }
              .padding(12)
              .backgroundColor('#FFFFFF')
              .borderRadius(8)
            }
          })
        }
        .width('100%')
        .height(240)
        .divider({ strokeWidth: 1, color: '#F1F3F5' })
      }
      .margin({ bottom: 24 })
      
      // 推荐应用
      Column() {
        Text('推荐应用')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .margin({ bottom: 12 })
        
        Grid() {
          ForEach(this.recommendApps, (app: AppInfo) => {
            GridItem() {
              Column() {
                Text(app.icon)
                  .fontSize(36)
                Text(app.name)
                  .fontSize(14)
                  .fontColor('#333333')
                  .margin({ top: 8 })
                  .maxLines(1)
                  .textOverflow({ overflow: TextOverflow.Ellipsis })
              }
              .padding(12)
              .backgroundColor('#FFFFFF')
              .borderRadius(12)
              .width('100%')
              .alignItems(HorizontalAlign.Center)
            }
          })
        }
        .columnsTemplate('1fr 1fr 1fr')
        .columnsGap(10)
        .rowsGap(10)
        .width('100%')
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .padding({ left: 16, right: 16 })
  }
}

七、常见问题与解决方案

7.1 网格布局异常

问题描述:网格项显示不完整或布局错乱。

解决方案

  1. 检查 columnsTemplate 的语法是否正确
  2. 确保网格项有明确的尺寸约束
  3. 检查是否有其他样式覆盖

7.2 网格项间距异常

问题描述:列间距或行间距不符合预期。

解决方案

  1. 检查 columnsGaprowsGap 的值
  2. 确认单位是否正确(px/vp/%)
  3. 检查父容器的 padding 是否影响

7.3 网格点击无响应

问题描述:点击网格项后,onClick 事件没有触发。

解决方案

  1. 检查 GridItem 是否有 enabled 属性设置为 false
  2. 确认网格项有足够的尺寸
  3. 检查是否有其他组件拦截了触摸事件

7.4 动态列数不生效

问题描述:更新 columnsTemplate 后,列数没有变化。

解决方案

  1. 确保使用状态变量(@State
  2. 检查模板字符串的格式是否正确
  3. 确认数据数组的长度足够

八、性能优化建议

8.1 避免复杂嵌套

简化网格项的布局结构:

// 避免
GridItem() {
  Column() {
    Row() {
      // 多层嵌套
    }
  }
}

// 推荐
GridItem() {
  Column() {
    // 扁平化布局
  }
}

8.2 合理设置列数

根据屏幕尺寸和内容选择合适的列数:

// 手机端
.columnsTemplate('1fr 1fr')

// 平板端
.columnsTemplate('1fr 1fr 1fr 1fr')

8.3 使用懒加载

对于大量数据,使用懒加载减少内存占用:

Grid() {
  ForEach(this.items, (item: string) => {
    GridItem() {
      // 只渲染可见的网格项
    }
  })
}

九、总结

Grid 组件是 HarmonyOS ArkUI 中功能强大的布局组件,掌握其使用方法对于构建高质量的用户界面至关重要。

核心要点

  1. 通过 columnsTemplate 配置列数和宽度
  2. 使用 columnsGaprowsGap 设置间距
  3. 通过 GridItem 定义网格项内容
  4. 支持动态调整列数,实现响应式布局
  5. 注意性能优化,避免复杂嵌套

希望本文能帮助你更好地理解和使用 Grid 组件,构建出优秀的 HarmonyOS 应用。


参考资料

Logo

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

更多推荐