鸿蒙HarmonyOS NEXT开发:常见性能优化场景-(Grid网格布局性能提升)

往期鸿蒙全套实战精彩文章必看内容:


概述

在实现如下图所示可滚动布局效果时,可能会通过columnStart/columnEnd对GridItem设置其占有行列数,实现不规则的布局效果。

图1 columnStart/columnEnd实现不规则网格布局

如果遇到如下的使用场景,使用columnStart/columnEnd可能会造成性能问题:

  1. 删除或拖拽等改变GridItem位置
  2. 使用scrollToIndex滑动到指定GridItem

Grid中存在大量GridItem,当使用columnStart/columnEnd、rowStart/rowEnd设置GridItem大小,该场景下耗时过长时,应该考虑使用GridLayoutOptions提升性能。使用columnStart/columnEnd、rowStart/rowEnd布局,在scrollToIndex滑动指定Index时,Grid会遍历GridItem去查找位置。而使用GridLayoutOptions布局,在scrollToIndex滑动指定Index时,则是通过计算方式去查找位置,查找GridItem位置效率更高。对此,可以通过GridLayoutOptions布局选项,配合rowsTemplate、columnsTemplate仅设置其中一个的Grid使用,替代通过columnStart/columnEnd控制GridItem占用多列的情况

案例说明

场景示例

下面介绍Grid中使用scrollToIndex滑动到指定位置的场景为例。在案例中采有了columnStart/columnEnd设置了不规则的宫格布局的反例,以及才有GridLayoutOption的正例进行对比,示例代码如下:

反例:使用columnStart,columnEnd设置GridItem大小。

// 导入性能打点模块
import hiTraceMeter from '@ohos.hiTraceMeter';

@Component
struct TextItem {
  @State item: string = "";

  build() {
    Text(this.item)
      .fontSize(16)
      .backgroundColor(0xF9CF93)
      .width('100%')
      .height(80)
      .textAlign(TextAlign.Center)
  }

  aboutToAppear() {
    // 结束打点任务
    hiTraceMeter.finishTrace("useColumnStartColumnEnd", 1);
  }
}

class MyDataSource implements IDataSource {
  private dataArray: string[] = [];

  public pushData(data: string): void {
    this.dataArray.push(data);
  }

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): string {
    return this.dataArray[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
  }
}

@Entry
@Component
struct GridExample {
  private datasource: MyDataSource = new MyDataSource();
  scroller: Scroller = new Scroller();

  aboutToAppear() {
    for (let i = 1; i <= 2000; i++) {
      this.datasource.pushData(i + '');
    }
  }

  build() {
    Column({ space: 5 }) {
      Text('使用columnStart,columnEnd设置GridItem大小').fontColor(0xCCCCCC).fontSize(9).width('90%')
      Grid(this.scroller) {
        LazyForEach(this.datasource, (item: string, index: number) => {
          if ((index % 4) === 0) {
            GridItem() {
              TextItem({ item: item })
            }
            .columnStart(0).columnEnd(2)
          } else {
            GridItem() {
              TextItem({ item: item })
            }
          }
        }, (item: string) => item)
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(10)
      .rowsGap(10)
      .width('90%')
      .height('40%')

      Button("scrollToIndex:1900").onClick(() => {
        // 开始打点任务
        hiTraceMeter.startTrace("useColumnStartColumnEnd", 1);
        this.scroller.scrollToIndex(1900);
      })
    }.width('100%')
    .margin({ top: 5 })
  }
}

正例:使用GridLayoutOptions设置GridItem大小,布局效果和反例保持一致。

// 导入性能打点模块
import hiTraceMeter from '@ohos.hiTraceMeter';

@Component
struct TextItem {
  @State item: string = "";

  build() {
    Text(this.item)
      .fontSize(16)
      .backgroundColor(0xF9CF93)
      .width('100%')
      .height(80)
      .textAlign(TextAlign.Center)
  }

  aboutToAppear() {
    // 结束打点任务
    hiTraceMeter.finishTrace("useGridLayoutOptions", 1);
  }
}

class MyDataSource implements IDataSource {
  private dataArray: string[] = [];

  public pushData(data: string): void {
    this.dataArray.push(data);
  }

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): string {
    return this.dataArray[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
  }
}

@Component
export struct GridExample2 {
  private datasource: MyDataSource = new MyDataSource();
  scroller: Scroller = new Scroller();
  private irregularData: number[] = [];
  layoutOptions: GridLayoutOptions = {
    regularSize: [1, 1],
    irregularIndexes: this.irregularData,
  };

  aboutToAppear() {
    for (let i = 1; i <= 2000; i++) {
      this.datasource.pushData(i + '');
      if ((i - 1) % 4 === 0) {
        this.irregularData.push(i - 1);
      }
    }
  }

  build() {
    Column({ space: 5 }) {
      Text('使用GridLayoutOptions设置GridItem大小').fontColor(0xCCCCCC).fontSize(9).width('90%')
      Grid(this.scroller, this.layoutOptions) {
        LazyForEach(this.datasource, (item: string, index: number) => {
          GridItem() {
            TextItem({ item: item })
          }
        }, (item: string) => item)
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(10)
      .rowsGap(10)
      .width('90%')
      .height('40%')

      Button("scrollToIndex:1900").onClick(() => {
        // 开始打点任务
        hiTraceMeter.startTrace("useGridLayoutOptions", 1);
        this.scroller.scrollToIndex(1900);
      })
    }.width('100%')
    .margin({ top: 5 })
  }
}

分析步骤

正反例采用相同的操作步骤,收集跳转过程中的性能参数,并进行对比:

  1. 打开Profiler工具,连接设备,选择对应的app进程

  2. 选择Frame,点击create Session开始测量收集数据

  3. 通过点击按钮,在调用scrollToIndex之前使用startTrace开始性能打点跟踪。

  4. 查看对应应用进程下的自定义打点事件,对应反例代码中定义的“useColumnStartColumnEnd”以及正例代码中的“useGridLayoutOptions”下的trace图。

    说明

    打点事件说明:当Grid查找到指定GridItem位置,准备渲染GridItem节点前,会进入GridItem自定义组件的生命周期回调aboutToAppear,在aboutToAppear里使用finishTrace停止性能打点跟踪。通过性能打点方式,使用startTrace标记调用scrollToIndex作为开始打点的位置,使用finishTrace标记查找到指定位置后准备渲染首个GridItem节点作为结束打点位置,来对比正反例场景下的耗时数据。

结果对比

如图所示,使用columnStart,columnEnd设置GridItem大小的布局方式,从自定义打点标签“H:useColumnStartColumnEndGrid”上可以看出从调用scrollToIndex到查找到指定Index准备构建GridItem节点耗时447ms。

图2 使用columnStart,columnEnd的打点信息

如图3所示,使用GridLayoutOptions设置GridItem大小的布局方式,从自定义打点标签“H:useGridLayoutOption”上可以看出从调用scrollToIndex到查找到指定Index准备构建GridItem节点耗时12ms。

图3 使用GridLayoutOptions的打点信息

从详细的trace进行分析可以发现,在“H:useColumnStartColumnEndGrid”打点标签时间段中存在大量的“H:Builder:BuildLazyItem”标签,可以发现Grid在查找指定的Index:1900是依次遍历Index来查找的。

图4 使用columnStart,columnEnd的放大trace标签信息

而使用GridLayoutOption的示例中,在“H:useGridLayoutOptions”打点标签时间段中只存在一个“H:Builder:BuildLazyItem”标签,可以发现Grid在查找指定Index:1900是直接一次查找到指定Index的。

图5 使用GridLayoutOptions的放大trace标签信息

通过上述分析可以发现,在相同布局情况下,使用columnStart,columnEnd设置GridItem大小方式,Grid在使用scrollToIndex查找指定Index时,会依次遍历GridItem节点,查找过程耗时较长。而使用GridLayoutOptions设置GridItem大小方式,是直接一次计算找到指定Index,查找过程耗时较短。所以使用GridLayoutOptions设置GridItem大小方式,在使用scrollToIndex滑动指定Index时,能够有效减少Grid加载时间,提升应用性能。

看完三件事❤️

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注作者 ,不定期分享原创知识。
  • 同时可以期待后续文章ing🚀。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值