HarmonyOS—UI开发性能提升的推荐方法

news2024/11/25 10:28:15

注:本文转载自HarmonyOS官网文档

开发者若使用低性能的代码实现功能场景可能不会影响应用的正常运行,但却会对应用的性能造成负面影响。本章节列举出了一些可提升性能的场景供开发者参考,以避免应用实现上带来的性能劣化。

使用数据懒加载

开发者在使用长列表时,如果直接采用循环渲染方式,如下所示,会一次性加载所有的列表元素,一方面会导致页面启动时间过长,影响用户体验,另一方面也会增加服务器的压力和流量,加重系统负

@Entry
@Component
struct MyComponent {
  @State arr: number[] = Array.from(Array(100), (v,k) =>k);  //构造0-99的数组
  build() {
    List() {
      ForEach(this.arr, (item: number) => {
        ListItem() {
          Text(`item value: ${item}`)
        }
      }, (item: number) => item.toString())
    }
  }
}

上述代码会在页面加载时将100个列表元素全部加载,这并非我们需要的,我们希望从数据源中按需迭代加载数据并创建相应组件,因此需要使用数据懒加载,如下所示:

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

  public totalCount(): number {
    return 0
  }

  public getData(index: number): any {
    return undefined
  }

  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)
    })
  }
}

class MyDataSource extends BasicDataSource {
  private dataArray: string[] = ['item value: 0', 'item value: 1', 'item value: 2']

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

  public getData(index: number): any {
    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)
  }
}

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

  build() {
    List() {
      LazyForEach(this.data, (item: string) => {
        ListItem() {
          Row() {
            Text(item).fontSize(20).margin({ left: 10 })
          }
        }
        .onClick(() => {
          this.data.pushData('item value: ' + this.data.totalCount())
        })
      }, item => item)
    }
  }
}

上述代码在页面加载时仅初始化加载三个列表元素,之后每点击一次列表元素,将增加一个列表元素。

设置List组件的宽高

在使用Scroll容器组件嵌套List组件加载长列表时,若不指定List的宽高尺寸,则默认全部加载。

说明

Scroll嵌套List时:

  • List没有设置宽高,会布局List的所有子组件。
  • List设置宽高,会布局List显示区域内的子组件。
  • List使用ForEach加载子组件时,无论是否设置List的宽高,都会加载所有子组件。
  • List使用LazyForEach加载子组件时,没有设置List的宽高,会加载所有子组件,设置了List的宽高,会加载List显示区域内的子组件。

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

  public totalCount(): number {
    return 0
  }

  public getData(index: number): any {
    return undefined
  }

  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)
    })
  }
}

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

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

  public getData(index: number): any {
    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)
  }
}

@Entry
@Component
struct MyComponent {
  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)
            }
          }
        })
      }
    }
  }
}

因此,此场景下建议设置List子组件的宽高。

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

  public totalCount(): number {
    return 0
  }

  public getData(index: number): any {
    return undefined
  }

  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)
    })
  }
}

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

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

  public getData(index: number): any {
    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)
  }
}

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

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

使用条件渲染替代显隐控制

如下所示,开发者在使用visibility通用属性控制组件的显隐状态时,仍存在组件的重新创建过程,造成性能上的损耗。

@Entry
@Component
struct MyComponent {
  @State isVisible: Visibility = Visibility.Visible;

  build() {
    Column() {
      Button("显隐切换")
        .onClick(() => {
          if (this.isVisible == Visibility.Visible) {
            this.isVisible = Visibility.None
          } else {
            this.isVisible = Visibility.Visible
          }
        })
      Row().visibility(this.isVisible)
        .width(300).height(300).backgroundColor(Color.Pink)
    }.width('100%')
  }
}

要避免这一问题,可使用if条件渲染代替visibility属性变换,如下所示:

@Entry
@Component
struct MyComponent {
  @State isVisible: boolean = true;

  build() {
    Column() {
      Button("显隐切换")
        .onClick(() => {
          this.isVisible = !this.isVisible
        })
      if (this.isVisible) {
        Row()
          .width(300).height(300).backgroundColor(Color.Pink)
      }
    }.width('100%')
  }
}

使用Column/Row替代Flex

由于Flex容器组件默认情况下存在shrink导致二次布局,这会在一定程度上造成页面渲染上的性能劣化。

@Entry
@Component
struct MyComponent {
  build() {
    Flex({ direction: FlexDirection.Column }) {
      Flex().width(300).height(200).backgroundColor(Color.Pink)
      Flex().width(300).height(200).backgroundColor(Color.Yellow)
      Flex().width(300).height(200).backgroundColor(Color.Grey)
    }
  }
}

上述代码可将Flex替换为Column、Row,在保证实现的页面布局效果相同的前提下避免Flex二次布局带来的负面影响。

@Entry
@Component
struct MyComponent {
  build() {
    Column() {
      Row().width(300).height(200).backgroundColor(Color.Pink)
      Row().width(300).height(200).backgroundColor(Color.Yellow)
      Row().width(300).height(200).backgroundColor(Color.Grey)
    }
  }
}

减少应用滑动白块

应用通过增大List/Grid控件的cachedCount参数,调整UI的加载范围。cachedCount表示屏幕外List/Grid预加载item的个数。

如果需要请求网络图片,可以在item滑动到屏幕显示之前,提前下载好内容,从而减少滑动白块。

如下是使用cachedCount参数的例子:

@Entry
@Component
struct MyComponent {
  private source: MyDataSource = new MyDataSource();

  build() {
    List() {
      LazyForEach(this.source, item => {
        ListItem() {
          Text("Hello" + item)
            .fontSize(50)
            .onAppear(() => {
              console.log("appear:" + item)
            })
        }
      })
    }.cachedCount(3) // 扩大数值appear日志范围会变大
  }
}

class MyDataSource implements IDataSource {
  data: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];

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

  public getData(index: number): any {
    return this.data[index]
  }

  registerDataChangeListener(listener: DataChangeListener): void {
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
  }
}

使用说明:

cachedCount的增加会增大UI的cpu、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。

点击关注阅读原文,了解更多精彩资讯

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/953006.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RT-Thread概述与体验

RTT目录结构 最核心的文件只有5个&#xff1a; src\thread.c&#xff1a;线程相关的文件&#xff0c;比如创建线程、启动线程。src\timer.c&#xff1a;定时器相关的文件。src\scheduler.c&#xff1a;调度器&#xff1a;维护线程的就绪链表、提供临界区的进出函数。src\ipc.c…

CRM系统有哪些类型?如何管理客户关系?

什么是是客户关系管理 Zoho CRM是一种经营策略帮助企业建立客户为中心的企业管理模式同时CRM也是一种技术手段帮助企业建立牢靠的客户关系&#xff0c;实现降本增效的目的。例如建立全渠道沟通&#xff0c;提高客户满意度&#xff0c;跟进客户细分开展个性化的营销活动提高市场…

uniapp--- 微信小程序 用户隐私新规相关代码调整【vue3+ts+uView框架】

uniapp— 微信小程序 用户隐私新规相关代码调整【vue3tsuView框架】 官方公告地址&#xff1a;https://developers.weixin.qq.com/community/develop/doc/00042e3ef54940ce8520e38db61801 用户隐私保护指引填写说明地址&#xff1a;https://developers.weixin.qq.com/miniprogr…

C++信息学奥赛1177:奇数单增序列

#include<bits/stdc.h> using namespace std; int main(){int n;cin>>n; // 输入整数 n&#xff0c;表示数组的大小int arr[n]; // 创建大小为 n 的整型数组for(int i0;i<n;i) cin>>arr[i]; // 输入数组元素for(int i0;i<n;i){ // 对数组进行冒泡排序f…

Resource Hacker下载

下载路径 Resource Hacker (angusj.com)http://www.angusj.com/resourcehacker/#download 应用方法示例 【2023年更新】手把手教你去除 WinRAR 的弹窗广告_winrar广告怎么去除_areosun的博客-CSDN博客https://blog.csdn.net/haiyunzhiqiu/article/details/119176038#comment…

管理类联考——逻辑——形式逻辑——汇总篇——知识点突破——性质模态

性质&模态 角度一 角度二 矛盾关系 【对象】(1)所有、有的不;(2)所有不、有的;(3)某个、某个不。 【典例】①所有偶像都是靠颜值的。 ②有的偶像不是靠颜值的。 试分析: (1)如果①为真,试判断②的真假。 (2)如果①为假,试判断②的真假。 (3)①和②是否可…

u盘提示需要格式化如何恢复里边的数据?你可以这么做

“昨天我和往常一样往U盘传输一些重要文件&#xff0c;突然收到一个提示&#xff1a;说我需要格式化它才能使用&#xff0c;但是我里面有重要的数据&#xff0c;所以我不敢点&#xff0c;可是没法复制出里面的数据&#xff0c;有没有人告诉我&#xff0c;如何恢复里边的数据&am…

【算法与数据结构】106、LeetCode从中序与后序遍历序列构造二叉树

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;首先我们要知道后序遍历数组的最后一个元素必然是根节点&#xff0c;然后根据根节点在中序遍历数组中的…

Zynq GTX全网最细讲解,aurora 8b/10b协议,OV5640板对板视频传输,提供2套工程源码和技术支持

目录 1、前言免责声明 2、我这里已有的 GT 高速接口解决方案3、GTX 全网最细解读GTX 基本结构GTX 发送和接收处理流程GTX 的参考时钟GTX 发送接口GTX 接收接口GTX IP核调用和使用 4、设计思路框架视频源选择OV5640摄像头配置及采集动态彩条视频数据组包GTX aurora 8b/10b数据对…

Docker Harbor 私有镜像仓库的部署和管理

目录 一、什么是Harbor 二、Harbor的特性 三、Harbor的构成 四、部署配置Docker Harbor 首先需要安装 Docker-Compose 服务 部署 Harbor 服务 修改配置文件 docker配置文件添加本地仓库地址 关于 Harbor.cfg 配置文件中有两类参数&#xff1a;所需参数和可选参数 &a…

双基证券:游戏版号发放整体趋势的向好将持续优化供给端

双基证券表示&#xff0c;版号发放整体趋势的向好将继续优化供应端&#xff0c;游戏新产品周期正逐渐开启&#xff0c;各家游戏公司盈余端将逐渐企稳&#xff0c;同时将推进游戏商场规划进一步增加。长时间来看&#xff0c;AIGC等技术对游戏全工业链具有降本增效&#xff0c;提…

节日书单视频怎么制作?这个工具不要错过

节日书单视频制作是一项具有一定专业性和科普性质的任务。在制作节日书单视频时&#xff0c;我们需要注意一些方法和注意事项&#xff0c;以确保视频的质量和效果。本文将介绍一些关键的步骤和技巧&#xff0c;帮助您顺利完成节日书单视频的制作。 编辑制作节日书单视频的话&am…

Python2021年9月Python二级 -- 编程题解析

题目一: 日期转换 编写一个程序&#xff0c;实现用户输入一个日期&#xff0c;格式为"月/日/年"&#xff0c;如输入05/21/2021&#xff0c;程序输出日期为’May 21,20211至12月的英文名称如下: January,February,March,April, May, June, July&#xff0c;August, Se…

YOLO目标检测——人脸表情识别数据集下载分享

受试者在15个视点和19个照明条件下成像&#xff0c;同时显示一系列面部表情&#xff0c;资源包括PIE照明子集&#xff08;1154张人脸灰度图&#xff0c;3232&#xff09;和三个Pose05、Pose07、Pose09子集&#xff08;分别包括3332张、1629张、859张人脸灰度图&#xff0c;6464…

RTP协议简介

介绍 RTP&#xff0c;real-time transport protocol&#xff0c;实时传输协议&#xff0c;用于传输具有实时属性的数据。RTP提供端到端的网络传输功能&#xff0c;适用于通过组播或单播网络服务传输实时数据(如音频、视频或仿真数据)的应用。 RTP协议层有两个子协议&#xff…

构建稳定的爬虫系统:如何选择合适的HTTP代理服务商

在构建一个稳定、高效的爬虫系统中&#xff0c;选择合适的HTTP代理服务商是至关重要的一步。本文将介绍如何选取可靠且性能优秀的HTTP代理服务供应商&#xff0c;来完成搭建一个强大而稳定的爬虫系统。 1.了解不同类型和特点 -免费公开代理服务器:提供免费但可能存在限制或不…

C++信息学奥赛1179:奖学金

#include<bits/stdc.h> using namespace std; int main(){int n;cin>>n; // 输入整数 n&#xff0c;表示数组的大小int id[n], arr[n], brr[n], crr[n]; // 创建大小为 n 的整型数组 id, arr, brr, crrfor(int i0;i<n;i) {id[i] i1; // 初始化 id 数组为 1,2,3…

使用maven创建springboot项目

创建maven快速启动项目 命令行或者idea、eclipse快捷创建也可以 pom.xml下project项目下导入springboot 父工程 <!--导入springboot 父工程--> <parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.bo…

Linux socket网络编程实战(tcp)实现双方聊天

在上节已经系统介绍了大致的流程和相关的API&#xff0c;这节就开始写代码&#xff01; 回顾上节的流程&#xff1a; 创建一个NET文件夹 来存放网络编程相关的代码&#xff1a; tcp服务端代码初步实现--上 这部分先实现服务器的连接部分的代码并进行验证 server1.c&#xff…

服务器中locked勒索病毒的原因和影响,勒索病毒解密,数据恢复

locked勒索病毒是一种流行的计算机病毒&#xff0c;它会对感染的计算机进行加密&#xff0c;并要求用户支付一定数量的赎金才能解锁。这种病毒已经存在了几年&#xff0c;对许多计算机用户造成了严重的困扰。 locked勒索病毒通常是通过电子邮件或恶意网站传播的。电子邮件中可能…