HarmonyOS鸿蒙Next中不使用嵌套Grid实现MultiType Grid效果

HarmonyOS鸿蒙Next中不使用嵌套Grid实现MultiType Grid效果 在这里使用GridLayoutOptions的irregularIndexes和onGetIrregularSizeByIndex来实现

图片

定义MultiType类型

enum ItemType {
  banner, //占整行
  label, //占整行
  column2, //一行2列
  column3 //一行3列
}

interface ItemEntity {
  type: ItemType
  color: ResourceColor
}

Grid代码

@Entry
@ComponentV2
struct GridPage {
  @Local array: ItemEntity[] = []
  @Local indexArray: number[] = []

  aboutToAppear(): void {
    this.array = getTestData()
    this.indexArray = Array.from<number, number>({ length: this.array.length }, (_, index) => index)
  }

  build() {
    Grid(undefined, {
      regularSize: [1, 1] ,
      irregularIndexes:this.indexArray,//1,2,3的最小公倍数是6,一行是6个columns,因为没有1行6个的item,所以所有的index都要设置
      onGetIrregularSizeByIndex:(index) =>{
        const type = this.array[index].type
        switch (type){
          case ItemType.banner:
          case ItemType.label:
            return [1,6]//1,2,3的最小公倍数是6,占满6个columns,所以就是占满整行
          case ItemType.column2:
            return [1,3]//占3个columns,就是一行2个(6/3=2)
          default :
            return [1,2]//占2个columns,就是一行3个(6/2=3)
        }
      },
    }) {
      Repeat(this.array)
        .templateId((item) =>{
          return item.type.toString()
        })
        .template(ItemType.banner.toString(),(rpItem) =>{//banner
          GridItem(){
            Text(rpItem.index.toString())
          }.backgroundColor(rpItem.item.color)
          .width('100%')
          .height(100)
        })
        .template(ItemType.label.toString(),(rpItem) =>{//label
          GridItem(){
            Text(rpItem.index.toString())
          }.backgroundColor(rpItem.item.color)
          .width('100%')
          .height(30)
        })
        .template(ItemType.column2.toString(),(rpItem) =>{//column2
          GridItem(){
            Text(rpItem.index.toString())
          }.backgroundColor(rpItem.item.color)
          .width('100%')
          .aspectRatio(1)
        })
        .each((rpItem)=>{//column3
            GridItem(){
              Text(rpItem.index.toString())
            }.backgroundColor(rpItem.item.color)
            .width('100%')
            .aspectRatio(1)
        })
        .virtualScroll({totalCount:this.array.length})
    }
    .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr') //1,2,3的最小公倍数是6
    .columnsGap(10)
    .rowsGap(10)
    .height('100%')
    .width('100%')
    .padding(10)
  }
}

测试数据:

function getTestData():ItemEntity[]{
  let array:ItemEntity[] = []
  //banner
  array.push({ type: ItemType.banner, color: Color.Red })
  //label
  array.push({ type: ItemType.label, color: Color.Grey })
  //column2
  for (let i = 0; i < 3; i++) {
    array.push({ type: ItemType.column2, color: Color.Pink })
  }
  //label
  array.push({ type: ItemType.label, color: Color.Grey })
  //column3
  for (let i = 0; i < 100; i++) {
    array.push({ type: ItemType.column3, color: Color.Orange })
  }
  return array

}

最终效果如上图。

存在的问题:

如果把测试的数据column2和column3之间lableitem去掉则会变成:

图片

图中红圈这个item怎么跑到上面一行去了!!

个人猜测这个第4行还剩下的宽度大于column3的宽度,所以Grid把它安排到第4行,没有把它安排到下一行。

出现这种情况在column2的最后一行可以填充空白的数据。


更多关于HarmonyOS鸿蒙Next中不使用嵌套Grid实现MultiType Grid效果的实战教程也可以访问 https://www.itying.com/category-93-b0.html

7 回复

这就是flexbox布局吧?

更多关于HarmonyOS鸿蒙Next中不使用嵌套Grid实现MultiType Grid效果的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


多一个选择吧,这里用Grid结合Repeat可以做到懒加载和item组件复用,数据量大的话内存开销和性能会好一些,如果就一页的数据用flexbox布局更方便。

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

在HarmonyOS Next中,可通过ArkUI的Grid组件配合ForEach循环实现多类型布局。使用条件渲染或自定义组件区分不同类型项,在数据源中定义类型标识,根据标识返回不同UI结构。利用Grid的columnsTemplate属性设置响应式列数,结合不同尺寸的GridItem实现多类型尺寸混合排列。

在HarmonyOS Next中,你遇到的问题是由于Grid布局引擎在计算不规则项位置时的行为导致的。当column2(占3列)项目后面直接跟着column3(占2列)项目时,如果当前行剩余空间大于等于2列但小于3列,column3项目会被放置在该行剩余空间,而不是强制换行。

要解决这个问题,关键在于精确控制每个项目的起始位置。GridLayoutOptions提供了onGetIrregularOffsetByIndex回调,你可以通过它指定每个项目的起始列:

Grid(undefined, {
  regularSize: [1, 1],
  irregularIndexes: this.indexArray,
  onGetIrregularSizeByIndex: (index) => {
    const type = this.array[index].type
    switch (type){
      case ItemType.banner:
      case ItemType.label:
        return [1, 6]
      case ItemType.column2:
        return [1, 3]
      default:
        return [1, 2]
    }
  },
  onGetIrregularOffsetByIndex: (index) => {
    // 计算每个项目应该开始的列
    let currentColumn = 0
    for (let i = 0; i < index; i++) {
      const type = this.array[i].type
      const columns = type === ItemType.banner || type === ItemType.label ? 6 : 
                     type === ItemType.column2 ? 3 : 2
      
      currentColumn += columns
      if (currentColumn >= 6) {
        currentColumn = currentColumn % 6
      }
    }
    
    // 如果当前行放不下,强制换行
    const currentType = this.array[index].type
    const neededColumns = currentType === ItemType.banner || currentType === ItemType.label ? 6 : 
                         currentType === ItemType.column2 ? 3 : 2
    
    if (currentColumn + neededColumns > 6) {
      return [0, 0] // 换到下一行开始
    }
    
    return [0, currentColumn]
  }
})

这种方法通过手动计算每个项目的起始位置,确保布局符合预期。对于需要换行的情况,返回[0, 0]让项目从新行开始。

另一种更简洁的方法是使用flexWrapjustifyContent属性,但需要结合GridItem的宽度设置:

Grid() {
  ForEach(this.array, (item, index) => {
    GridItem() {
      // 内容
    }
    .width(this.getItemWidth(item.type))
    .height(this.getItemHeight(item.type))
  })
}
.columnsTemplate('1fr')
.flexWrap(FlexWrap.Wrap)
.justifyContent(FlexAlign.Start)

这种方法更接近传统flex布局,但需要手动计算每个项目的宽度百分比(例如,column2项目宽度为'50%'column3项目宽度为'33.33%')。

你遇到的问题确实是因为Grid的自动布局算法在寻找可用空间时的行为。使用onGetIrregularOffsetByIndex可以更精确地控制布局,确保视觉一致性。

回到顶部