HarmonyOS ArkUI训练营入门-组件掌握系列-Grid 网格布局深度解析-PC版本

概述

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


一、Grid 组件基础

1.1 组件定义与作用

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

typescript 复制代码
@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 基础使用示例

typescript 复制代码
@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 配置列数和宽度:

typescript 复制代码
@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 不等宽布局

创建不同宽度的列:

typescript 复制代码
@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 固定宽度布局

使用固定像素宽度:

typescript 复制代码
@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 设置间距:

typescript 复制代码
@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 背景与边框

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

typescript 复制代码
@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 网格项样式

定制网格项的外观:

typescript 复制代码
@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 圆角与阴影

添加圆角和阴影效果:

typescript 复制代码
@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 完整样式示例

typescript 复制代码
@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 动态列数

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

typescript 复制代码
@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 结合媒体查询

根据屏幕尺寸调整布局:

typescript 复制代码
@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 带图标的网格

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

typescript 复制代码
@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 图片网格

创建图片墙效果:

typescript 复制代码
@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 网格点击事件

为网格项添加点击事件:

typescript 复制代码
@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 嵌套网格

在网格中嵌套其他布局:

typescript 复制代码
@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 代码实现

typescript 复制代码
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 避免复杂嵌套

简化网格项的布局结构:

typescript 复制代码
// 避免
GridItem() {
  Column() {
    Row() {
      // 多层嵌套
    }
  }
}

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

8.2 合理设置列数

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

typescript 复制代码
// 手机端
.columnsTemplate('1fr 1fr')

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

8.3 使用懒加载

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

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

九、总结

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

核心要点

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

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


参考资料

相关推荐
Funing72 小时前
FreeRTOS学习day1:Keil 工程配置与 FreeRTOS 链表机制理解
数据结构·学习·链表
心中有国也有家2 小时前
使用 DevEco Studio 配置 Flutter 鸿蒙签名
学习·flutter·华为·harmonyos
灯澜忆梦2 小时前
MySQL 完整学习路线
学习·mysql
如此风景2 小时前
鸿蒙开发状态管理 V2 全部装饰器详解速查表
harmonyos
爱吃大芒果3 小时前
鸿蒙 ArkTS 网络工程进阶:@ohos.net.http 模块下的长连接、超时与防内存泄漏实战
华为·harmonyos
FrameNotWork3 小时前
HarmonyOS 6.0 沉浸式状态栏与安全区域适配实战
华为·harmonyos
绝世番茄3 小时前
鸿蒙原生ArkTS布局方式之Stack+Opacity叠加淡入淡出深度解析
华为·harmonyos·鸿蒙
森林古猿13 小时前
再论斜率优化
c++·学习·算法
心中有国也有家4 小时前
鸿蒙Flutter开发环境从零搭建教程(Windows/macOS双平台·避坑版)
学习·flutter·华为·harmonyos
国服第二切图仔4 小时前
HarmonyOS APP《画伴梦工厂》开发第43篇-多模块架构设计——模块拆分与依赖管理
华为·harmonyos