HarmonyOS NEXT应用元服务布局合理使用布局组件

鸿蒙时代
发布于 2025-6-20 15:40
浏览
0收藏

选择合适的布局组件
在布局时,子组件会根据父组件的布局算法得到相应的排列规则,然后按照规则进行子组件位置的摆放。不同的布局容器使用的布局算法对性能带来的影响不同。开发者应该根据场景选用合适的布局,除非必须,尽量减少使用性能差的布局组件。
我们常用的基础布局组件包含以下组件:
使用Row、Column构建线性布局。
使用Stack构建层叠布局。
上述布局都属于线性布局,顾名思义,在线性布局中排布的组件是按照特定的方向线性放置,如横向/纵向/Z序方向。除上述布局类型外,还有一些复杂布局能力,如Flex、List、Grid、RelativeContainer和自定义布局等。
使用Flex构建弹性布局。
List既具备线性布局的特点,同时支持懒加载和滑动的能力。
Grid/GridItem提供了宫格布局的能力,同时也支持懒加载和滑动能力。
RelativeContainer是一种相对布局,通过描述各个内容组件间相互关系来指导内容元素的布局过程,可从横纵两个方面进行布局描述,是一种二维布局算法。
复杂布局提供了场景化的能力,解决一种或者多种布局场景。但是在一些场景下,不恰当的使用这些高级组件,可能带来更多的性能消耗。
我们通过对不同的布局方式,设置对应容器相同的嵌套深度为5、总元素节点为20个Text的情况下,来对比其性能消耗。通过Profiler工具获取其首帧绘制时间进行对比。对比结果如下表:
HarmonyOS NEXT应用元服务布局合理使用布局组件-鸿蒙开发者社区
说明
以上数据来源均为版本DevEco Studio 4.0.3.415、SDK 4.0.10.9条件下测试得到,不同设备类型数据可能存在差异,测试数据旨在体现性能优化趋势,仅供参考。
可以发现,在布局深度和节点数相同的情况下:
使用基础组件如Column和Row容器的性能明显高于其他布局。
Flex的性能明显低于Column和Row容器,这是由于Flex本身带来的二次布局的影响。
Grid/GridItem布局、相对布局RelativeContainer的性能消耗高于基础组件Column、Row、Stack等容器。
以上数据都是基于相同布局层数和节点数的情况下的对比结果,反应了布局本身的相对性能消耗,并不意味着使用了该组件性能就一定差,也并非任何情况下使用基础组件都能够保持良好的性能,因为在一些情况下,使用高级组件能够大大减少嵌套层数和节点数,其带来的性能提升反而高于组件本身的性能消耗。所以在使用布局时尽量遵循以下原则:
在相同嵌套层级的情况下,如果多种布局方式可以实现相同布局效果,优选低耗时的布局,如使用Column、Row替代Flex实现相同的单行布局。
在能够通过其他布局大幅优化节点数的情况下,可以使用高级组件替代,如使用RelativeContainer替代Row、Column实现扁平化布局,此时其收益大于布局组件本身的性能差距。
仅在必要的场景下使用高耗时的布局组件,如使用Flex实现折行布局、使用Grid实现二维网格布局等。
Scroll嵌套List场景下,给定List组件宽高
在使用Scroll容器组件嵌套List组件加载长列表时,若不指定List的宽高尺寸,则默认全部加载。
说明
Scroll嵌套List时:
List没有设置宽高时,List的所有子组件ListItem都会参与布局。
List设置宽高,只有布局区域内的ListItem子组件会参与布局。
List使用ForEach加载子组件时,无论是否设置List的宽高,都会加载所有子组件。
List使用LazyForEach加载子组件时,没有设置List的宽高,会加载所有子组件,设置了List的宽高,会加载List显示区域内的子组件。
在如下代码案例中,通过Scroll嵌套List,对比List设置宽度和不设置的情况下:

class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: string[] = [];

  public totalCount(): number {
    return 0;
  }

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

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener');
      this.listeners.push(listener);
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener');
      this.listeners.splice(pos, 1);
    }
  }

  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }

  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }

  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
    })
  }
}


export class MyDataSource extends BasicDataSource {
  private dataArray: Array<string> = new Array(100).fill('test');

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


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


  public addData(index: number, data: string): void {
    this.dataArray.splice(index, 0, data);
    this.notifyDataAdd(index);
  }


  public pushData(data: string): void {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1);
  }
}

segment1.ets
不设置宽高的代码样例如下:

import { MyDataSource } from './segment1';

@Entry
@Component
struct NotSetHeightTestPage {
  private data: MyDataSource = new MyDataSource();

  build() {
    Scroll() {
      List() {
        LazyForEach(this.data, (item: string, index: number) => {
          ListItem() {
            Row() {
              Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
            }
          }
        })
      }
    }
  }
}

segment2.ets
设置固定高度的代码如下:

import { MyDataSource } from './segment1';


@Entry
@Component
struct SetHeightTestPage {
  private data: MyDataSource = new MyDataSource();

  build() {
    Scroll() {
      List() {
        LazyForEach(this.data, (item: string, index: number) => {
          ListItem() {
            Row() {
              Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
            }
          }
        })
      }.width('100%').height(500)
    }
  }
}

segment3.ets
通过DevEco Studio的Profiler抓取launch数据可以得到如下:
图2 List宽高不固定
HarmonyOS NEXT应用元服务布局合理使用布局组件-鸿蒙开发者社区
图3 List宽高固定
HarmonyOS NEXT应用元服务布局合理使用布局组件-鸿蒙开发者社区
表9 不设置List宽高与设置宽高对比数据
HarmonyOS NEXT应用元服务布局合理使用布局组件-鸿蒙开发者社区
未设置宽高的情况下,在进行布局时,从FlushLayoutTask以及FlushRenderTask的数据可以看到参与布局的组件数量是100个,设置了List宽高的情况下,从FlushLayoutTask以及FlushRenderTask的数据可以看到参与布局的组件数量是12个。说明对于Scroll嵌套List的情况下,如果不设置List宽高,由于Scroll是可滚动容器,其高度为无穷大,List在不设置的高度的情况下,高度也为无穷大,所以此时会创建所有的内容。而设置了高度的情况下,只会创建给定高度内的组件内容,所以为12个。体现在布局时间上,没有设置宽高的情况下,总体布局时间是32.43ms,设置了固定宽高数值的情况下,时间为6.08ms,大幅提升了首次加载时的性能。
本文主要引用整理于鸿蒙官方文档

分类
收藏
回复
举报
回复
    相关推荐