HarmonyOS—UI开发性能提升的推荐方法
注:本文转载自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、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。
点击关注阅读原文,了解更多精彩资讯
相关文章:
HarmonyOS—UI开发性能提升的推荐方法
注:本文转载自HarmonyOS官网文档 开发者若使用低性能的代码实现功能场景可能不会影响应用的正常运行,但却会对应用的性能造成负面影响。本章节列举出了一些可提升性能的场景供开发者参考,以避免应用实现上带来的性能劣化。 使用数据懒加载 开…...
英文科技论文写作与发表-常见英语写作困扰(第3章)
1.常见英语写作困扰 英语写作应该是越精炼越好。写完一个句子,建议尝试删除某个或某些单词,如果删除后句子意义基本不变,就应该删除。 1.1 所有格 使用所有格可以省去至少一个单词(of),如:Kangs book T…...
video标签自动播放音视频并绘制波形图
html中的<video>标签可以用来播放常见的音视频格式,支持的格式包括:MP3、Ogg、WAV、AAC、MP4、WebM、AVI等,当然支持的格式也和浏览器和操作系统有关。这里以一个可以自动播放音视频并绘制波形图的页面为例说明一下<video>标签的用法。 vid…...
Netty—EventLoop
文章目录 一、EventLoopGroup 是什么?🤔️二、NioEventLoop 有哪些重要组成部分?🔍三、NioEventLoop 的 thread 在何时启动?三、 run() 方法中线程在干嘛? 一、EventLoopGroup 是什么?…...
[极客大挑战 2019]FinalSQL(bypass盲注)
这里是数字型注入,选择一个序号 fuzz ?id1这里过滤了很多东西 使用fuzzSQL字典,这是我自己定义编写的一个fuzz字典,内容较少 select from information . tables whereand " or | & union columns updatexml extractvalue databa…...
如何实现小程序与h5页面间的跳转
接到新需求,要在小程序页面内点击按钮实现跳转h5,一开始没接触过,还挺头疼的,但真正做起来,也就那么一回事啦,废话少说,直接上 1. 配置域名 先登录小程序开发平台,将页面需要跳转的…...
企业架构LNMP学习笔记9
nginx配置文件定义php-fpm服务: 编写测试文件: vim /usr/local/nginx/html/index.php 内容: <?phpphpinfo(); 在nginx的配置文件中配置: 修改配置文件,告知nginx如果收到.php结尾的请求,交由给php-…...
华为OD机试 - 二维伞的雨滴效应(Java JS Python)
题目描述 普通的伞在二维平面世界中,左右两侧均有一条边,而两侧伞边最下面各有一个伞坠子,雨滴落到伞面,逐步流到伞坠处,会将伞坠的信息携带并落到地面,随着日积月累,地面会呈现伞坠的信息。 1、为了模拟伞状雨滴效应,用二叉树来模拟二维平面伞(如下图所示),现在输…...
【HttpRunnerManager】搭建接口自动化测试平台操作流程
一、需要准备的知识点 1. linux: 安装 python3、nginx 安装和配置、mysql 安装和配置 2. python: django 配置、uwsgi 配置 二、我搭建的环境 1. Centos7 (配置 rabbitmq、mysql 、Supervisord) 2. python 3.6.8 (配置 django、uwsgi&am…...
【C++】STL-常用算法-常用查找算法
0.前言 1.find #include <iostream> using namespace std;// 常用查找算法 find #include<vector> #include<algorithm>//查找 内置数据类型 void test01() {vector<int>v;for (int i 0; i < 10; i){v.push_back(i);}//查找 容器中 是否有 5 这个元…...
vue3 webpack打包流程及安装 (1)
npm run build 也可以打包 如果没有特殊需求 可以使用 效果其实是差不多的 --------------------------------------------------------------------------------------------------------------------------------- webpack网址 : 起步 | webpack 中文文档 (docsc…...
【C++】内联函数 ① ( 内联函数引入 | 内联函数语法 )
文章目录 一、内联函数引入1、内联函数引入2、代码示例 - 宏代码片段 与 内联函数 二、内联函数语法1、内联函数语法说明2、代码示例 - 内联函数基本语法 一、内联函数引入 1、内联函数引入 " 内联函数 " 是 C 语言中的一种特殊函数 , 其目的是为了提高程序的执行效率…...
聊聊springboot的ConfigurationProperties的绑定
序 本文主要研究一下springboot的ConfigurationProperties的绑定 ConfigurationPropertiesBindingPostProcessor org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java /*** {link BeanPostProcessor} to bind {link PropertySo…...
Mysql和Oracle的语法区别?
Mysql和Oracle是两种不同的关系型数据库。 MySQL通常在中小型应用程序、Web应用程序和小型企业中广泛使用,因为它易于学习和部署,而且成本较低。 Oracle数据库通常用于大型企业和复杂的企业级应用程序,因为它提供了高度可扩展性、高可用性…...
F - LIS on Tree
F - LIS on Tree (atcoder.jp) 问题描述:树上LIS。 普通LIS。O(n * n)。 void solve() {int n; cin>>n;vector<int> f(n 1),a(n1);for(int i 1; i < n; i) {cin>>a[i];f[i] 1;for(int j 1; j < i; j) {if(a[i] > a[j]) f[i] max…...
2023 年全国大学生数学建模B题目-多波束测线问题
B题目感觉属于平面几何和立体几何的问题,本质上需要推导几何变换情况,B题目属于有标准答案型,没太大的把握不建议选择,可发挥型不大。 第一问 比较简单,就一个2维平面的问题,但有点没理解,这个…...
qt creater11 翻译国际化教程教程:
先出效果图。 闲聊几句:qt这个翻译很方便,能直接导出项目里所有文字。 具体步骤如下: 在Qt中,我们可以使用QTranslator类来实现多语言切换。以下是一般步骤: 1. 在你的源代码中,所有需要翻译的字符串都…...
【AWS实验 】在 AWS Fargate 上使用 Amazon ECS 部署应用程序
文章目录 实验概览目标实验环境任务 1:连接到实验命令主机任务 2:将应用程序容器化任务 3:构建 Web2048 容器任务 4:创建 Amazon ECR 存储库并推送 Docker 映像任务 5:创建 ECS 集群任务 6:测试应用程序总结…...
matlab几种求解器的选择fsolve-sole-vpasolve
文章目录 fsolvesolvevpasovle总结vpasovle的结果fsovle的结果 fsolve 求数值解 result_xfsolve(my_fun,x0,options)参数: my_fun:待求解函数,作为一个.m文件 x0:初始值,向量,可以仅仅指定其中的几项solve 强大的求解器。在方程组中求解析…...
无限访问 GPT-4,OpenAI 强势推出 ChatGPT 企业版!
继 ChatGPT 收费大降价、推出 App 版等系列动作之后,OpenAI 于今日宣布正式发布面向企业的 AI 助手——ChatGPT Enterprise 版。 与 To C 端的 ChatGPT 版本有所不同的是,该版本可以以更快速度无限制地访问 GPT-4,还可以用来处理更长输入的上…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
