鸿蒙开发中Grid组件的全面解析与实战

本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

一、Grid组件基础

1.1 Grid组件概述

Grid是鸿蒙ArkUI框架提供的网格布局容器,由"行"和"列"分割的单元格组成,通过指定子组件所在的单元格实现各种布局效果。Grid组件从API Version 7开始支持,后续版本不断增强了其功能。

核心特点

  • 支持自定义行列数和尺寸占比
  • 允许子组件跨越多行或多列
  • 提供水平和垂直两种布局方向
  • 支持滚动、懒加载等性能优化特性
  • 可与GridItem子组件配合实现复杂布局

1.2 Grid与GridItem关系

Grid的直接子组件必须是GridItem,这是网格布局的基本规则。GridItem用于定义网格中的单个项目,可以包含其他子组件。

重要说明

  • Grid子组件的索引值按顺序递增计算
  • if/else条件分支中,只有成立的分支会参与索引计算
  • ForEach/LazyForEach会展开所有子节点计算索引
  • visibility属性为Hidden或None时仍会计算索引 

二、Grid核心API与属性

2.1 布局控制API

2.1.1 行列模板(rowsTemplate/columnsTemplate)

通过设置行列模板确定网格的基本结构:

Grid() {
  // 子组件
}
.rowsTemplate('1fr 1fr 1fr') // 3行,每行高度相等
.columnsTemplate('1fr 2fr 1fr') // 3列,中间列宽度是两侧的2倍

高级用法

  • 使用repeat()函数简化重复模式
  • auto-fill/auto-fit实现自适应布局
// 自动填充列,最小列宽100vp
.columnsTemplate("repeat(auto-fit, 100vp)") 

// 按容器高度10%自动计算行数
.rowsTemplate("repeat(auto-fill, 10%)") 
2.1.2 行列间距(columnsGap/rowsGap)

控制网格行列之间的间距:

Grid()
  .columnsGap(10)  // 列间距10vp
  .rowsGap('5%')   // 行间距占容器高度的5%

注意:负值会被处理为0,优先级高于子组件边距

2.1.3 布局方向(layoutDirection)

控制子项的排列方向:

Grid()
  .layoutDirection(GridDirection.Row) // 默认,从左到右排列
  // 或 GridDirection.Column 从上到下排列

2.2 GridItem属性

GridItem用于定义网格中的单个项目,关键属性包括:

GridItem() {
  // 子组件
}
.rowStart(0)    // 起始行号
.rowEnd(1)      // 结束行号
.columnStart(0) // 起始列号
.columnEnd(1)   // 结束列号
.selectable(true) // 是否可被鼠标框选

注意事项

  • 行列号从0开始计数
  • 合理范围是0到总行数/列数-1
  • 只有设置了columnTemplate和rowTemplate的Grid中,这些属性才有效 

2.3 滚动控制API

2.3.1 滚动条设置
Grid()
  .scrollBar(BarState.Auto)    // 自动显示滚动条(API 10+默认)
  .scrollBarColor('#FFAABB')  // 自定义颜色
  .scrollBarWidth(6)          // 宽度6vp

支持的状态:

  • BarState.Off:隐藏滚动条
  • BarState.On:常显滚动条
  • BarState.Auto:滚动时显示 
2.3.2 滚动监听
Grid()
  .onScrollIndex((firstIndex: number) => {
    console.log('当前首项索引:', firstIndex)
  })

可用于实现分页加载等高级功能

2.4 性能优化API

2.4.1 懒加载缓存(cachedCount)
Grid()
  .cachedCount(3) // 上下各缓存3屏数据

仅对LazyForEach生效,平衡内存与流畅度

2.4.2 强制重建(forceRebuild)
GridItem()
  .forceRebuild(true) // 数据变更时重新计算布局

适用于动态增删或修改跨行跨列属性的场景

三、Grid高级特性

3.1 编辑模式(editMode)

启用拖拽排序功能(API 8+):

Grid()
  .editMode(true) // 允许用户拖拽GridItem调整位置

需配合onItemDragStart/onItemDragMove事件实现完整交互

3.2 多选模式(multiSelectable)

启用鼠标框选(API 8+):

Grid()
  .multiSelectable(true) // 配合onSelect事件获取选中项索引

适用于文件管理器等需要批量操作的场景

3.3 不规则布局(GridLayoutOptions)

处理跨行跨列的高级配置(API 10+):

Grid({ layoutOptions: {
  regularSize: [1, 1],          // 常规项占1行1列
  irregularIndexes: [0, 5],     // 指定不规则项的索引
  onGetIrregularSizeByIndex: (index) => {
    return (index === 0) ? [2, 1] : [1, 2] // 第0项跨2行,第5项跨2列
  }
}})

注意:垂直滚动Grid不支持跨多行,水平滚动不支持跨多列

3.4 响应式断点(GridRow/GridCol)

针对不同屏幕尺寸动态调整列数:

GridRow({
  columns: { xs: 2, sm: 4, md: 8, lg: 12 }, // 不同断点列数
  gutter: { x: 12, y: 8 },                  // 行列间距
  breakpoints: ['320vp', '520vp', '840vp']  // 断点阈值
}) {
  GridCol({ span: { xs: 1, lg: 2 } }) { /* 子项 */ }
}

实现自适应栅格系统,常用于仪表盘布局

四、Grid实战示例

4.1 基础网格布局

@Entry
@Component
struct BasicGridExample {
  build() {
    Grid() {
      GridItem() {
        Text('会议').fontSize(20)
      }.backgroundColor('#D4F2E7')

      GridItem() {
        Text('签到').fontSize(20)
      }.backgroundColor('#87CEFA')

      GridItem() {
        Text('投票').fontSize(20)
      }.backgroundColor('#FFF0F5')

      GridItem() {
        Text('打印').fontSize(20)
      }.backgroundColor('#E6E6FA')
    }
    .rowsTemplate('1fr 1fr')
    .columnsTemplate('1fr 1fr')
    .columnsGap(10)
    .rowsGap(15)
    .height('100%')
    .width('100%')
  }
}

4.2 可滚动网格布局

@Component
struct ScrollableGridExample {
  @State services: string[] = ['直播', '进口', '秒杀', '超市', '充值', '机票', '酒店', '电影', '理财', '美食']

  build() {
    Column({ space: 5 }) {
      Grid() {
        ForEach(this.services, (service: string) => {
          GridItem() {
            Text(service)
              .fontSize(16)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .width('25%')
          .height(80)
          .backgroundColor('#FFFFFF')
        })
      }
      .rowsTemplate('1fr 1fr') // 只设置rowsTemplate,内容超出时可水平滚动
      .rowsGap(15)
      .height('80%')
      .width('100%')
    }
    .height('100%')
    .width('100%')
    .backgroundColor('#F1F3F5')
  }
}

4.3 不规则网格

// 不规则商品列表
@Entry
@Component
struct ECommerceGridExample {
  @State productList: Product[] = [
    { id: 1, type: 'banner' },
    { id: 2, type: 'normal' },
    { id: 3, type: 'normal' },
    { id: 4, type: 'normal' },
    { id: 5, type: 'promotion' },
    // 更多商品...
  ]

  build() {
    Grid({ layoutOptions: {
      regularSize: [1, 1],
      irregularIndexes: [0, 4],
      onGetIrregularSizeByIndex: (index) => 
        index === 0 ? [2, 2] : [1, 2]
    }}) {
      LazyForEach(this.productList, (item: Product, index: number) => {
        GridItem() {
          if (item.type === 'banner') {
            Image('banner.jpg')
              .objectFit(ImageFit.Cover)
          } else if (item.type === 'promotion') {
            Text('限时促销')
              .fontSize(18)
              .textAlign(TextAlign.Center)
          } else {
            Column() {
              Image('product.jpg')
                .width(80)
                .height(80)
              Text(`商品 ${item.id}`)
                .fontSize(14)
            }
          }
        }
        .backgroundColor('#FFF')
        .rowStart(index === 0 ? 1 : 2)
        .columnEnd(index === 4 ? 3 : 1)
      })
    }
    .columnsTemplate('1fr 1fr 1fr')
    .rowsTemplate('repeat(auto-fill, 200vp)')
    .columnsGap(8)
    .rowsGap(12)
    .scrollBar(BarState.Auto)
    .cachedCount(2)
    .height('100%')
    .width('100%')
  }
}

class Product {
  id: number;
  type: string;
}

效果说明

  • 首项为大图广告(跨2×2)
  • 第5项为横向促销栏(跨2列)
  • 支持懒加载与流畅滚动 

五、总结

5.1 性能优化建议

  1. 大数据集处理

    • 单页渲染超过50项时强制使用LazyForEach
    • 合理设置cachedCount(通常2-3)
    • 避免在GridItem中使用复杂计算 
  2. 渲染优化

    • 使用@Recycle装饰器复用组件
    • 简化GridItem内部结构
    • 避免频繁更新Grid布局属性 
  3. 内存管理

    • 对于动态内容,及时清理不可见项的资源
    • 监控Grid的内存使用情况
    • 在页面隐藏时释放非必要资源 

5.2 兼容性

  1. 版本差异

    • editMode、multiSelectable等特性需API 8+
    • GridLayoutOptions需要API 10+
    • 开发前需确认目标设备的API支持情况
  2. 布局限制

    • Grid直接子组件必须是GridItem
    • 复杂布局需在GridItem内部嵌套其他容器
    • 跨行跨列有方向限制(垂直滚动不支持跨多行等) 
  3. 调试技巧

    • 使用开发者工具的布局检查器
    • 开启Grid的边框显示辅助调试
    • 使用console.log输出布局计算的关键值

 

<think>我们计划开发一个商城分类页面,包含以下核心功能:1.分类侧边栏(左侧)2.商品列表(右侧)3.分类商品的联动设计思路:-使用Row布局,左侧为分类列表(垂直滚动),右侧为商品网格列表(垂直滚动)-点击分类项时,右侧商品列表自动滚动到对应分类的商品区域-右侧商品列表滚动时,左侧分类项自动高亮当前显示的分类技术要点:1.使用`List`组件实现分类侧边栏2.使用`Grid`或`List`组件实现商品列表3.使用`Scroll`和`Scroller`实现联动滚动4.使用`@State`和`@Link`管理状态数据结构:```typescript//分类interfaceCategory{id:number;name:string;icon:ResourceStr;//图标资源}//商品interfaceProduct{id:number;categoryId:number;name:string;price:number;image:ResourceStr;}```页面结构:```mermaidgraphLRA[分类页面]-->B[左侧分类列表]A-->C[右侧商品列表]B-->B1[分类项1]B-->B2[分类项2]C-->C1[分类1商品网格]C-->C2[分类2商品网格]```代码实现步骤:1.定义分类页面组件结构2.初始化分类和商品数据3.构建左侧分类列表4.构建右侧商品列表(按分类分组)5.实现点击分类滚动到对应商品区域6.实现滚动商品列表时更新左侧选中分类具体实现:</think># 鸿蒙商城分类页面开发指南 ## 设计思路功能规划 基于ArkTS在鸿蒙系统中实现商城分类页面,需要包含以下核心功能: ```mermaid graph TD A[分类页面] --> B[顶部导航栏] A --> C[侧边分类导航] A --> D[商品网格布局] A --> E[筛选排序功能] A --> F[交互反馈机制] ``` ## 实现代码 ### 1. 定义数据结构 ```typescript // 商品分类 interface Category { id: number; name: string; icon: ResourceStr; } // 商品信息 interface Product { id: number; name: string; price: number; discount?: number; // 折扣率 sales: number; // 销量 stock: number; // 库存 image: ResourceStr; categoryId: number; } // 筛选条件 interface FilterOptions { sortBy: 'default' | 'priceAsc' | 'priceDesc' | 'sales'; priceRange: [number, number]; onlyInStock: boolean; } ``` ### 2. 分类页面实现 ```typescript @Entry @Component struct CategoryPage { // 分类数据 @State categories: Category[] = [ { id: 1, name: '手机数码', icon: $r('app.media.category_phone') }, { id: 2, name: '电脑办公', icon: $r('app.media.category_computer') }, { id: 3, name: '家用电器', icon: $r('app.media.category_appliance') }, { id: 4, name: '美妆护肤', icon: $r('app.media.category_beauty') }, { id: 5, name: '食品生鲜', icon: $r('app.media.category_food') }, { id: 6, name: '服装鞋包', icon: $r('app.media.category_clothes') }, { id: 7, name: '母婴玩具', icon: $r('app.media.category_baby') }, { id: 8, name: '运动户外', icon: $r('app.media.category_sports') }, ]; // 商品数据 @State products: Product[] = [ { id: 101, name: '华为P60 Pro', price: 5988, sales: 2560, stock: 120, image: $r('app.media.product_phone1'), categoryId: 1 }, { id: 102, name: 'iPhone 15', price: 6999, sales: 1890, stock: 85, image: $r('app.media.product_phone2'), categoryId: 1 }, // 更多商品数据... ]; @State selectedCategoryId: number = 1; @State filterOptions: FilterOptions = { sortBy: 'default', priceRange: [0, 10000], onlyInStock: false }; // 筛选后的商品列表 @State filteredProducts: Product[] = []; aboutToAppear() { this.filterProducts(); } // 筛选商品 filterProducts() { // 1. 按分类筛选 let result = this.products.filter(p => p.categoryId === this.selectedCategoryId); // 2. 按库存筛选 if (this.filterOptions.onlyInStock) { result = result.filter(p => p.stock > 0); } // 3. 按价格范围筛选 result = result.filter(p => p.price >= this.filterOptions.priceRange[0] && p.price <= this.filterOptions.priceRange[1] ); // 4. 排序 switch (this.filterOptions.sortBy) { case 'priceAsc': result.sort((a, b) => a.price - b.price); break; case 'priceDesc': result.sort((a, b) => b.price - a.price); break; case 'sales': result.sort((a, b) => b.sales - a.sales); break; } this.filteredProducts = result; } // 切换分类 changeCategory(id: number) { this.selectedCategoryId = id; this.filterProducts(); } // 改变排序方式 changeSort(method: 'default' | 'priceAsc' | 'priceDesc' | 'sales') { this.filterOptions.sortBy = method; this.filterProducts(); } // 跳转商品详情 navigateToDetail(productId: number) { // 实际项目中通过路由跳转 console.log(`跳转到商品详情: ${productId}`); } // 商品卡片组件 @Builder productCard(product: Product) { Column({ space: 8 }) { // 商品图片 Stack({ alignContent: Alignment.TopEnd }) { Image(product.image) .width('100%') .aspectRatio(1) .objectFit(ImageFit.Cover) .borderRadius(8) // 折扣标签 if (product.discount) { Text(`${(product.discount * 100).toFixed(0)}折`) .fontSize(12) .fontColor(Color.White) .padding({ left: 6, right: 6, top: 2, bottom: 2 }) .backgroundColor('#ff5500') .borderRadius(4) .margin(8) } } // 商品名称 Text(product.name) .fontSize(14) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) .width('100%') // 价格区域 Row({ space: 4 }) { Text(`¥${product.price.toFixed(2)}`) .fontSize(16) .fontColor('#ff5500') .fontWeight(FontWeight.Bold) if (product.discount) { Text(`¥${(product.price / product.discount).toFixed(2)}`) .fontSize(12) .fontColor('#999') .decoration({ type: TextDecorationType.LineThrough }) } } // 销量和库存 Row({ space: 8 }) { Text(`销量: ${product.sales}`) .fontSize(12) .fontColor('#666') Text(product.stock > 0 ? '有货' : '缺货') .fontSize(12) .fontColor(product.stock > 0 ? '#07c160' : '#ff5500') } } .width('100%') .padding(8) .backgroundColor(Color.White) .borderRadius(12) .onClick(() => this.navigateToDetail(product.id)) } // 分类项组件 @Builder categoryItem(category: Category) { Column({ space: 8 }) { Image(category.icon) .width(40) .height(40) Text(category.name) .fontSize(14) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) } .padding(12) .width('100%') .backgroundColor(this.selectedCategoryId === category.id ? '#fff2e8' : Color.White) .borderRadius(8) .onClick(() => this.changeCategory(category.id)) } // 筛选工具栏 @Builder filterToolbar() { Row({ space: 16 }) { // 综合排序 Button('综合', { type: ButtonType.Capsule }) .stateEffect(this.filterOptions.sortBy === 'default') .backgroundColor(this.filterOptions.sortBy === 'default' ? '#fff2e8' : '#f5f5f5') .fontColor(this.filterOptions.sortBy === 'default' ? '#ff5500' : '#333') .onClick(() => this.changeSort('default')) // 价格排序 Button('价格', { type: ButtonType.Capsule }) .stateEffect(true) .backgroundColor( this.filterOptions.sortBy === 'priceAsc' || this.filterOptions.sortBy === 'priceDesc' ? '#fff2e8' : '#f5f5f5' ) .fontColor( this.filterOptions.sortBy === 'priceAsc' || this.filterOptions.sortBy === 'priceDesc' ? '#ff5500' : '#333' ) .onClick(() => { if (this.filterOptions.sortBy === 'priceDesc') { this.changeSort('priceAsc'); } else { this.changeSort('priceDesc'); } }) // 销量排序 Button('销量', { type: ButtonType.Capsule }) .stateEffect(this.filterOptions.sortBy === 'sales') .backgroundColor(this.filterOptions.sortBy === 'sales' ? '#fff2e8' : '#f5f5f5') .fontColor(this.filterOptions.sortBy === 'sales' ? '#ff5500' : '#333') .onClick(() => this.changeSort('sales')) // 筛选按钮 Button('筛选', { type: ButtonType.Capsule }) .backgroundColor('#f5f5f5') .fontColor('#333') .onClick(() => { // 实际项目中打开筛选面板 console.log('打开筛选面板'); }) } .width('100%') .padding(12) .backgroundColor(Color.White) } build() { Row() { // 左侧分类导航 Scroll() { Column() { ForEach(this.categories, (category: Category) => { this.categoryItem(category) }) } .padding(8) } .scrollBar(BarState.Off) .width('25%') // 右侧商品区域 Column() { // 筛选工具栏 this.filterToolbar() // 商品网格 Scroll() { Grid() { ForEach(this.filteredProducts, (product: Product) => { GridItem() { this.productCard(product) } }) } .columnsTemplate('1fr 1fr') .columnsGap(8) .rowsGap(8) .padding(8) } .scrollBar(BarState.Off) .width('100%') } .layoutWeight(1) .backgroundColor('#f5f5f5') } .width('100%') .height('100%') .backgroundColor('#f5f5f5') } } ``` ## 核心技术解析 ### 1. 响应式数据管理 - 使用`@State`装饰器管理分类和商品数据状态 - 通过`filterProducts`方法实现数据的动态筛选和排序 - 当分类选择或筛选条件变化时,自动更新商品列表 ### 2. 布局设计 - **侧边分类导航**:使用`Scroll`+`Column`实现垂直滚动分类列表 - **商品网格**:使用`Grid`组件实现两列商品展示 - **筛选工具栏**:使用`Row`+`Button`实现水平排列的筛选选项 ### 3. ArkTS特性应用 - **@Builder装饰器**:创建可复用的UI组件(`productCard`和`categoryItem`) - **条件渲染**:根据商品状态显示不同UI元素(折扣标签、库存状态) - **样式扩展**:使用链式调用设置组件样式 ### 4. 交互设计 - **分类选择**:点击分类项高亮显示并刷新商品列表 - **商品点击**:跳转到商品详情页 - **排序切换**:点击排序按钮改变商品排序方式 ## 性能优化建议 1. **懒加载优化**: ```typescript // 使用LazyForEach优化长列表性能 LazyForEach(this.filteredProducts, (product: Product) => { GridItem() { this.productCard(product) } }) ``` 2. **图片资源优化**: ```typescript Image(product.image) .width('100%') .aspectRatio(1) .objectFit(ImageFit.Cover) .borderRadius(8) .interpolation(ImageInterpolation.High) // 高质量插值 .renderMode(ImageRenderMode.Original) // 原始质量渲染 ``` 3. **数据分页加载**: ```typescript @State currentPage: number = 1; @State pageSize: number = 20; loadMoreData() { // 模拟分页加载 const startIndex = (this.currentPage - 1) * this.pageSize; const newData = this.allProducts.slice(startIndex, startIndex + this.pageSize); this.products = [...this.products, ...newData]; this.currentPage++; } // 在Scroll组件中添加滚动到底部事件 .onReachEnd(() => { this.loadMoreData(); }) ``` ## 相关扩展功能 ### 1. 添加搜索功能 ```typescript @State searchKeyword: string = ''; // 在筛选方法中添加搜索条件 if (this.searchKeyword) { result = result.filter(p => p.name.includes(this.searchKeyword) ); } // 在顶部添加搜索框 Search({ placeholder: '搜索商品' }) .onChange(value => { this.searchKeyword = value; this.filterProducts(); }) ``` ### 2. 添加购物车快捷操作 ```typescript // 在商品卡片中添加购物车按钮 Button($r('app.media.icon_cart'), { type: ButtonType.Circle }) .width(32) .height(32) .position({ x: '85%', y: '85%' }) .onClick(() => { // 添加到购物车逻辑 }) ``` ## 相关问题 1. 如何实现分类页面商品详情页的数据传递?[^1] 2. 如何处理大量分类数据的性能优化问题? 3. 如何在ArkTS中实现自定义的下拉刷新功能?[^2] 4. 如何在不同屏幕尺寸上适配分类页面的布局?[^3] [^1]: 鸿蒙实战:ArkTs 开发一个鸿蒙应用。ArkTS 通过 struct 声明自定义组件名,使用 @Entry 和 @Component 装饰的自定义组件作为页面的入口。 [^2]: 鸿蒙HarmonyOS开发框架—ArkTS语言(基本语法 一)_鸿蒙 使用 ts。除此之外,ArkTS扩展了多种语法范式来使开发更加便捷。 [^3]: 针对一些列因素,整理了一套纯血版鸿蒙HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值