#我的鸿蒙开发手记#网格与瀑布流布局浅析 原创 精华

珲少
发布于 2025-5-12 09:32
浏览
0收藏

网格布局

网格布局从布局结构上看与栅格布局类似,其核心也是将页面分为指定的行和列,相比栅格布局,网格布局更加灵活,不仅可以进行列的合并,也可以对行进行合并。
网格布局的核心是提供行列定义模版,模板本身是一个字符串,行和列分别进行定义,字符串中的fr个数表示行或列的个数,fr前的数字表示此行或列对应的尺寸占比,例如下面的代码:

Grid() {
  ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9], (index: number)=>{
    GridItem() {
      Text(`数据${index}`).fontSize(30).textAlign(TextAlign.Center)
    }.backgroundColor(Color.Red)
  })
}.rowsTemplate('1fr 2fr 1fr').columnsTemplate('1fr 2fr 1fr').columnsGap(10).rowsGap(10)

上面代码中的’1fr 2fr 1fr’即是模版的定义,运行效果如图所示。
#我的鸿蒙开发手记#网格与瀑布流布局浅析-鸿蒙开发者社区

Grid在使用时,要注意有如下规则:
1.Grid容器的子组件必须为GridItem组件。
2.行列模板同时进行了设置时,将会展示固定行列个数的元素,整个容器不可滚动。
3.当只设置了行或列的模板时,元素按照设置的方向进行布局,超出的内容可以滚动。
4.当行和列的模版都没有设置时,其行列数由子元素的尺寸决定,整个容器不可滚动。
通过行列模版,我们定义的是规则的网格结构,Grid容器也支持定义不规则的网格结构,在构造Grid实例时,其有两个参数,分别为:

参数 类型 意义
scroller Scroller 可滚动时,控制容器的滚动以及获取相关信息。
layoutOptions GridLayoutOptions 布局参数。

layoutOptions布局参数中可以实现onGetRectByIndex配置项,此配置项需要设置为一个函数,用来返回指定索引数据的布局规则。示例代码如下:

Grid(undefined, {
  regularSize:[1, 1],
  onGetRectByIndex: (index: number)=>{
    // 注意此处的index从0开始,表示数据下标
    if (index == 0) {
      // 从第1行第1列开始,占1行3列
      return [0, 0, 1, 3]
    }
    if (index == 3) {
      // 从第2行第3列开始,占2行1列
      return [1, 2, 2, 1]
    }
    if (index == 7) {
      // 从第4行第2列开始,占1行2列
      return [3, 1, 1, 2]
    }
    return undefined
  }
}) {
  ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], (index: number)=>{
    GridItem() {
      Text(`数据${index}`).fontSize(30).textAlign(TextAlign.Center)
    }.backgroundColor(Color.Red)
  })
}.rowsTemplate('1fr 1fr 1fr 1fr').columnsTemplate('1fr 1fr 1fr').columnsGap(10).rowsGap(10)

其中regularSize用来指定标准的子组件布局尺寸,目前只支持一个子组件占单行单列,此参数为后续扩展预留。onGetRectByIndex用来具体设置某个元素的位置和占用行列数,其设置的函数的参数index为元素索引,从0开始,此函数如果返回undefined则表示默认的布局规则,不做额外改变。我们也可以返回一个包含4个数值元素的数组,4个数值分别表示要布局子组件的:起始行索引,起始列索引,占用行数、占用列数。运行上面的代码,效果如图:
#我的鸿蒙开发手记#网格与瀑布流布局浅析-鸿蒙开发者社区
需要注意,在设置某个索引位置元素的布局规则时,需要符合布局逻辑,子组件将按照顺序进行布局,如果设置的位置已经被其他子组件占用,则设置无效。同样如果起始位置没有被占用但是其他位置被占用,则只会展示GridItem子组件的一部分。
关于使用Scroller对Grid容器的滚动进行控制,以及相关滚动事件的监听,其用法与List容器完全一致,这里就不再赘述。

瀑布流布局

瀑布流布局常用在展示宽度或高度不规则的信息流中,常见于电商或资讯类软件中。在ArkUI中使用WaterFlow创建瀑布流容器,WaterFlow支持横向滚动也支持纵向滚动,对于横向滚动的瀑布流,可以通过rowsTemplate方法设置行数与对应占比的模版。对于纵向滚动的瀑布流,可以通过columnsTemplate设置列数以及对应占比的模版。
纵向滚动的瀑布流在布局时,通常子元素的宽度固定,高度动态,当发生换行时,新的元素会被布局在高度最小的列下面,同理对于横向布局的瀑布流,子元素高度固定,宽度动态,发生换列时,新的元素会优先布局在宽度最小的行后面。我们若要手动实现这样的布局逻辑会非常复杂,使用WaterFlow则非常简单。例如:

WaterFlow() {
  ForEach(this.data, (title: string)=>{
    FlowItem() {
      Text(title).textAlign(TextAlign.Center).fontSize(35)
    }.width('100%')
    .height(Math.max(Math.random() * 300, 50))
    .backgroundColor(Color.Red)
  })
}.rowsGap(10).columnsGap(10)
.layoutDirection(FlexDirection.Column).columnsTemplate('1fr 1fr')

#我的鸿蒙开发手记#网格与瀑布流布局浅析-鸿蒙开发者社区
将子组件的宽度改为随机,并设置WaterFlow的布局方向为横向,即可创建横向的瀑布流,如下:

WaterFlow() {
  ForEach(this.data, (title: string)=>{
    FlowItem() {
      Text(title).textAlign(TextAlign.Center).fontSize(35)
    }.height('100%')
    .width(Math.max(Math.random() * 300, 50))
    .backgroundColor(Color.Red)
  })
}.rowsGap(10).columnsGap(10)
.layoutDirection(FlexDirection.Row).rowsTemplate('1fr 1fr 1fr 1fr')

#我的鸿蒙开发手记#网格与瀑布流布局浅析-鸿蒙开发者社区
WaterFlow的子组件必须时FlowItem,这种布局结构常用来渲染无限滚动的信息流,尤其是在有大量的图片信息时,由于图片的宽高比例不定,使用瀑布流布局效果会非常美观。在实际项目开发中,WaterFlow通常会和LazyForEach懒加载组合使用。
WaterFlow在构造时参数支持的配置项如下:

配置项 类型 意义
footer CustomBuilder 设置尾视图。
scroller Scroller 滚动控制器。
sections WaterFlowSections 分组控制器。
layoutMode WaterFlowLayoutMode 瀑布流布局模式。

WaterFlow也支持配置尾视图以及分组,通过分组允许不同的组采用不同的布局规则,通过sections参数,我们可以为不同的分组设置不同的行/列数,不同的间距等。例如下面的示例代码:

@Component
struct WaterFlowLayout {
  data: string[] = (() => {
    let d: string[] = []
    for (let i = 0; i < 100; i++) {
      d.push(`${i}`)
    }
    return d
  })()

  sections = new WaterFlowSections()

  aboutToAppear(): void {
    let sec1: SectionOptions = {
      itemsCount: 4,
      crossCount: 2,
      columnsGap: 5,
      rowsGap: 5,
      margin: {top: 30}
    }
    let sec2: SectionOptions = {
      itemsCount: 8,
      crossCount: 4,
      columnsGap: 10,
      rowsGap: 10,
      margin: {top: 30}
    }

    let sec3: SectionOptions = {
      itemsCount: 88,
      crossCount: 1,
      rowsGap: 15,
      margin: {top: 30}
    }
    let sections = [
      sec1, sec2, sec3
    ]
    this.sections.splice(0, 0, sections)
  }

  build() {
    WaterFlow({sections: this.sections}) {
      ForEach(this.data, (title: string)=>{
        FlowItem() {
          Text(title).textAlign(TextAlign.Center).fontSize(35)
        }.width('100%')
         .height(Math.max(Math.random() * 100, 50))
        .backgroundColor(Color.Red)
      })
    }.rowsGap(10).columnsGap(10)
    .layoutDirection(FlexDirection.Column).columnsTemplate('1fr 1fr')
  }
}

其中WaterFlowSections可以理解为分组管理器,其中封装了方法来操作分组数据,如示例代码中的splice方法,此方法用来删除、替换或新增分组数据,第1个参数为要操作的分组索引起始位置,第2个参数为要删除的分组个数,第3个参数为添加一组分组数据。具体的分组数据由SectionOptions 类进行定义,此类中提供的配置属性如下:

属性名 类型 意义
itemsCount number 当前分组中的元素个数。
crossCount number 轨数,即交叉轴方向上的行/列数。
columnsGap Dimension 设置列间距。
rowsGap Dimension 设置行间距。
margin Margin | Dimension 设置当前分组的外边距。

WaterFlowSections实例操作分组的方法列举如下:

方法名 参数 意义
splice start: number deleteCount: number sections: Array<SectionOptions> 从指定的索引开始删除多个分组,同时添加多个分组。
push SectionOptions 添加一个分组。
update sectionIndex: number section: SectionOptions 更新指定位置的分组。
values 获取当前所有分组数据。
length 获取当前分组个数。

上面的分组示例代码运行效果如图所示。
#我的鸿蒙开发手记#网格与瀑布流布局浅析-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2025-5-12 09:32:07修改
收藏
回复
举报
回复
    相关推荐