HarmonyOS开发实战( Beta5版)Swiper高性能开发指南

news2024/9/20 0:54:12

背景

在应用开发中,Swiper 组件常用于翻页场景,比如:桌面、图库等应用。Swiper 组件滑动切换页面时,基于按需加载原则通常会在下一个页面将要显示时才对该页面进行加载和布局绘制,这个过程包括:

  • 如果该页面使用了@Component 装饰的自定义组件,那么自定义组件的 build 函数会被执行并创建内部的 UI 组件;

  • 如果使用了LazyForEach,会执行 LazyForEach 的 UI 生成函数生成 UI 组件;

  • 在 UI 组件构建完成后,会对 UI 组件进行布局测算和绘制。

针对复杂页面场景,该过程可能会持续较长时间,导致滑动过程中出现卡顿,对滑动体验造成负面影响,甚至成为整个应用的性能瓶颈。如在图库大图浏览场景中,若不使用预加载机制,每次都将在滑动开始的首帧去加载下一张图片,会导致首帧耗时过长甚至掉帧,拖慢应用性能。

为了解决上述问题,可以使用 Swiper 组件的预加载机制,利用主线程的空闲时间来提前构建和布局绘制组件,优化滑动体验。

使用场景

如果开发者的应用场景属于加载较为耗时的场景时,尤其是下列场景,推荐使用 Swiper 预加载功能。

  • Swiper 的子组件大于等于五个;

  • Swiper 的子组件具有复杂的动画;

  • Swiper 的子组件加载时需要执行网络请求等耗时操作;

  • Swiper 的子组件包含大量需要渲染的图像或资源。

Swiper 预加载机制说明

预加载机制是 Swiper 组件中一个重要的特性,允许 Swiper 滑动到下一个子组件之前提前加载后续页面的内容,其主要目的是提高应用滑动时的流畅性和响应速度。当用户尝试滑动到下一个子组件时,如果下一个子组件的内容已经提前加载完毕,那么滑动就会立即发生,否则 Swiper 组件需要在加载下一个子组件的同时处理滑动事件,对滑动体验造成负面影响。当前 Swiper 组件的预加载在用户滑动离手动效开始时触发,离手动效的计算在渲染线程中进行,因此主线程有空闲的时间可以进行预加载的操作。配合 LazyForEach 的按需加载和销毁能力,可以在优化滑动体验基础上节省内存占用。

使用指导

  • 预加载子组件的个数在cachedCount属性中配置。

Swiper 共 5 页,当开发者设置了 cacheCount 属性为 1 且 loop 属性为 false 时,预加载的结果如下:

loop=false


 Swiper 共 5 页,当开发者设置了 cacheCount 属性为 1 且 loop 属性为 true 时,预加载的结果如下:

loop=true

  • Swiper 组件的子组件使用LazyForEach动态加载和销毁组件。

示例

class MyDataSource implements IDataSource { // LazyForEach的数据源
  private list: number[] = [];

  constructor(list: number[]) {
    this.list = list;
  }

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

  getData(index: number): number {
    return this.list[index];
  }

  registerDataChangeListener(_: DataChangeListener): void {
  }

  unregisterDataChangeListener(): void {
  }
}

@Component
struct SwiperChildPage { // Swiper的子组件
  @State arr: number[] = [];

  aboutToAppear(): void {
    for (let i = 1; i <= 100; i++) {
      this.arr.push(i);
    }
  }

  build() {
    Column() {
      List({ space: 20 }) {
        ForEach(this.arr, (index: number) => {
          ListItem() {
            Text(index.toString())
              .height('4.5%')
              .fontSize(16)
              .textAlign(TextAlign.Center)
              .backgroundColor(0xFFFFFF)
          }
          .border({ width: 2, color: Color.Green })
        }, (index: number) => index.toString());
      }
      .height("95%")
      .width("95%")
      .border({ width: 3, color: Color.Red })
      .lanes({ minLength: 40, maxLength: 40 })
      .alignListItem(ListItemAlign.Start)
      .scrollBar(BarState.Off)

    }.width('100%').height('100%').padding({ top: 5 });
  }
}

@Entry
@Preview
@Component
struct SwiperExample {
  private dataSrc: MyDataSource = new MyDataSource([]);

  aboutToAppear(): void {
    let list: Array<number> = []
    for (let i = 1; i <= 10; i++) {
      list.push(i);
    }
    this.dataSrc = new MyDataSource(list);
  }

  build() {
    Column({ space: 5 }) {
      Swiper() {
        LazyForEach(this.dataSrc, (_: number) => {
          SwiperChildPage();
        }, (item: number) => item.toString());
      }
      .loop(false)
      .cachedCount(1) // 提前加载后一项的内容
      .indicator(true)
      .duration(100)
      .displayArrow({
        showBackground: true,
        isSidebarMiddle: true,
        backgroundSize: 40,
        backgroundColor: Color.Orange,
        arrowSize: 25,
        arrowColor: Color.Black
      }, false)
      .curve(Curve.Linear)

    }.width('100%')
    .margin({ top: 5 })
  }
}

验证效果

为了更好地体现 Swiper 预加载机制带来的性能优化效果,用例采用下列前置条件:

  • Swiper 的子组件为带有 100 个 ListItem 的 List 组件;

  • Swiper 组件共有 10 个 List 子组件。

在该场景下,使用 Swiper 预加载机制可以为每个翻页动作节省约40%的时间,同时保证翻页时不丢帧,保证翻页的流畅度。

优化建议

由于组件构建和布局计算需要一定时间,cachedCount 的数量也不是设置得越大越好,过大的 cachedCount 可能会导致应用性能降低。当前 Swiper 组件滑动离手后的动效时间大约是 400ms,如果应用加载一个子组件的时间在 100ms~200ms 之间,为了在离手动效时间内完成组件的预加载,cachedCount 属性建议设置为 1 或 2,设置过大会导致主线程阻塞而产生卡顿。

那么方案可以继续优化,在抛滑场景时,Swiper 组件有一个OnAnimationStart回调接口,切换动画开始时触发该回调。此时,主线程空闲,应用可以充分利用这段时间进行图片等资源的预加载,减少后续 cachedCount 范围内的节点预加载耗时; 跟手滑动阶段不会触发OnAnimationStart回调,只有在离手后做切换动画(也就是抛滑阶段)才会触发。

示例

Swiper 子组件页面代码如下:

在子组件首次构建(生命周期执行到aboutToAppear)时,先判断 dataSource 中该 index 的数据是否有数据,若无数据则先进行资源加载,再构建节点。若有数据,则直接构建节点即可。

import image from '@ohos.multimedia.image';
import { MyDataSource } from './Index';

@Component
export struct PhotoItem { //Swiper的子组件
  myIndex: number = 0;
  private dataSource: MyDataSource = new MyDataSource([]);
  context = getContext(this);
  @State imageContent: image.PixelMap | undefined = undefined;

  aboutToAppear(): void {
    console.info(`aboutToAppear` + this.myIndex);
    this.imageContent = this.dataSource.getData(this.myIndex)?.image;
    if (!this.imageContent) { // 先判断dataSource中该index的数据是否有数据,若无数据则先进行资源加载
      try {
        // 获取resourceManager资源管理器
        const resourceMgr = this.context.resourceManager;
        // 获取rawfile文件夹下item.jpg的ArrayBuffer
        let str = "item" + (this.myIndex + 1) + ".jpg";
        resourceMgr.getRawFileContent(str).then((value) => {
          // 创建imageSource
          const imageSource = image.createImageSource(value.buffer);
          imageSource.createPixelMap().then((value) => {
            console.info("aboutToAppear push" + this.myIndex)
            this.dataSource.addData(this.myIndex, { description: "" + this.myIndex, image: value })
            this.imageContent = value;
          })
        })
      } catch (err) {
        console.error("error code" + err);
      }
    }
  }

  build() {
    Column() {
      Image(this.imageContent)
        .width("100%")
        .height("100%")
    }
  }
}

Swiper 主页面的代码如下:

import Curves from '@ohos.curves';
import { PhotoItem } from './PhotoItem'
import image from '@ohos.multimedia.image';

interface MyObject {
  description: string,
  image: image.PixelMap,
};

export class MyDataSource implements IDataSource {
  private list: MyObject[] = []

  constructor(list: MyObject[]) {
    this.list = list
  }

  totalCount(): number {
    return this.list.length
  }

  getData(index: number): MyObject {
    return this.list[index]
  }

  registerDataChangeListener(listener: DataChangeListener): void {
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
  }

  addData(index: number, data: MyObject) {
    this.list[index] = data;
  }
}

@Entry
@Component
struct Index {
  @State currentIndex: number = 0;
  cacheCount: number = 1
  swiperController: SwiperController = new SwiperController();
  private data: MyDataSource = new MyDataSource([]);
  context = getContext(this);

  aboutToAppear() {
    let list: MyObject[] = []
    for (let i = 0; i < 6; i++) {
      list.push({ description: "", image: this.data.getData(this.currentIndex)?.image })
    }
    this.data = new MyDataSource(list)
  }

  build() {
    Swiper(this.swiperController) {
      LazyForEach(this.data, (item: MyObject, index?: number) => {
        PhotoItem({
          myIndex: index,
          dataSource: this.data
        })
      })
    }
    .cachedCount(this.cacheCount)
    .curve(Curves.interpolatingSpring(0, 1, 228, 30))
    .index(this.currentIndex)
    .indicator(true)
    .loop(false)
    // 在OnAnimationStart接口回调中进行预加载资源的操作
    .onAnimationStart((index: number, targetIndex: number) => {
      console.info("onAnimationStart " + index + " " + targetIndex);
      if (targetIndex !== index) {
        try {
          // 获取resourceManager资源管理器
          const resourceMgr = this.context.resourceManager;
          // 获取rawfile文件夹下item.jpg的ArrayBuffer
          let str = "item" + (targetIndex + this.cacheCount + 2) + ".jpg";
          resourceMgr.getRawFileContent(str).then((value) => {
            // 创建imageSource
            const imageSource = image.createImageSource(value.buffer);
            imageSource.createPixelMap().then((value) => {
              this.data.addData(targetIndex + this.cacheCount + 1, {
                description: "" + (targetIndex + this.cacheCount + 1),
                image: value
              })
            })
          })
        } catch (err) {
          console.error("error code" + err);
        }
      }
    })
    .width('100%')
    .height('100%')
  }
}

总结

  • Swiper 组件的预加载机制与 LazyForEach 结合使用,能够达到最佳优化效果。

  • 预加载的 cachedCount 并非越大越好,需要结合单个子组件加载耗时来设置。假设一个子组件的加载耗时为 Nms,那么 cachedCount 推荐设置为小于 400/N。

  • 如果应用有非常高的性能优化需求,Swiper 预加载机制可搭配 OnAnimationStart 接口回调使用,进一步提升预加载的效率。

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

希望这一份鸿蒙学习文档能够给大家带来帮助~

GitCode - 全球开发者的开源社区,开源代码托管平台


 鸿蒙(HarmonyOS NEXT)最新学习路线

​​

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频教程+学习PDF文档

(鸿蒙语法ArkTS、TypeScript、ArkUI教程……)

 纯血版鸿蒙全套学习文档(面试、文档、全套视频等)

                   

鸿蒙APP开发必备

​​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

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

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

相关文章

学习算法需要数学知识吗?

目录 算法与数学:看似不可分割的关系常见算法中的数学元素案例分析:不需要高深数学知识的算法1. 二分查找2. 深度优先搜索 (DFS)3. 动态规划:斐波那契数列 如何在有限的数学背景下学习算法1. 专注于算法的逻辑和过程2. 可视化算法流程3. 从简单的实现开始,逐步优化4. 学习算法设…

centos7使用ifconfig查看IP,终端无ens33信息解决办法

1.问题描述 大概有十几天没用虚拟机&#xff0c;最后一次用忘记关闭虚拟机系统了&#xff1b;突然&#xff0c;发现我用远程连接工具&#xff0c;连接不上&#xff0c;去到虚拟机内部查看IP发现终端竟然没有输出enss33地址信息&#xff0c;额&#xff0c;就像下面这样。 2.解决…

android so的加载流程(Android 13~14)

序言 分析环境: Android 13~14 其实大佬 << 安卓so加载流程源码分析 >> 已经写得非常好了,我就没必要再写了 建议读者看看这篇文字,比较新,质量很高<< 安卓so加载流程源码分析 >> 为什么要分析 android so的加载流程 ??? 我想明白 so是怎么打…

无人机之反制系统篇

无人机的反制系统是一个复杂而精细的系统&#xff0c;旨在应对无人机的不当使用或潜在威胁。该系统通常由多个关键部分组成&#xff0c;包括搜索系统、光电跟踪系统、射频干扰系统及显控单元等&#xff0c;这些部分共同协作以实现对无人机的有效反制。以下是对无人机反制系统的…

SpringBoot开发——初步了解SpringBoot

文章目录 一、SpringBoot简介1、什么是Spring Boot2、Spring Boot的优点3、Spring Boot功能 二、Spring与Spring Boot对比三、Spring Boot与Spring MVC四、Spring Boot体系结构五、Springboot Initializr1、Spring Initializr2、Spring Initializr模块 一、SpringBoot简介 1、…

docker ps 得到的ports列的含义

前言 每次使用docker ps 查询容器运行情况的时候就很容易搞混ports列的含义&#xff0c;今天浅记一下 docker ps ports列含义 首先看docker ps的查询结果显示&#xff1a; CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 容器ID …

Axure中继器教程及案例详解

Axure RP 是一款强大的原型设计工具&#xff0c;广泛应用于产品设计、UI/UX 设计及交互设计中。中继器&#xff08;Repeater&#xff09;作为 Axure 中的一个重要元件&#xff0c;以其强大的数据处理和动态交互能力&#xff0c;成为设计师们不可或缺的工具。本文将从中继器基础…

LLM agentic模式之multi-agent: ChatDev,MetaGPT, AutoGen思路

文章目录 Multi-agentChatDev设计阶段编码阶段测试阶段文档编写 MetaGPTSOP模式下的Agent通信协议带执行反馈的迭代编程 AutoGenconversable agentsConversation ProgrammingAutoGen的应用 参考资料 Multi-agent ChatDev ChatDev出自2023年7月的论文《ChatDev: Communicative…

告别文档处理烦恼,PDF Guru Anki一键搞定所有

前言 知识就像烛光&#xff0c;能照亮一个人&#xff0c;也能照亮无数人&#xff0c;科技之光更是如此&#xff1b;这一理念深刻地影响了我们如何看待和应用新技术。正是在这样的背景下&#xff0c;一款集PDF处理与高效学习工具于一体的软件——PDF Guru Anki应运而生&#xf…

AI论文生成可靠吗?分享6款AI论文题目生成器网站

在当今学术研究和写作领域&#xff0c;AI论文题目生成器网站的出现极大地简化了学术写作流程。这些工具不仅能够帮助用户快速生成高质量的论文题目&#xff0c;还能提供文献推荐、论文润色等功能&#xff0c;从而提高写作效率和质量。以下是六款值得推荐的AI论文题目生成器网站…

glsl着色器学习(六)

准备工作已经做完&#xff0c;下面开始渲染 gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);gl.clearColor(0.5, 0.7, 1.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);gl.enable(gl.DEPTH_TEST); gl.enable(gl.CULL_FACE);设置视口 gl.viewport(0,…

轻量级的git-server工具:docker部署gogs

背景 创建一个自己使用的git server&#xff0c;让平时使用的代码之类的可以直接传到自己的服务器上&#xff0c;进行远程管理。由于一个人使用&#xff0c;gitlab 太重&#xff0c;所以选择gogs来实现功能。 系统&#xff1a;openEuler 22.03 (LTS-SP3) ip: 192.168.100.31 …

【Linux】Linux项目自动化构建工具 - make/Makefile

目录 一、make/Makefile的背景二、make/Makefile的基本概念三、依赖关系四、依赖方法五、make/Makefile原理六、Makefile的伪目标七、Makefile的变量八、Makefile的推导能力结尾 一、make/Makefile的背景 一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若…

【Java 基础】:三大特征之多态

✨ 杏花疏影里&#xff0c;吹笛到天明 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;java学习 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&…

JVM参数有哪些?

JVM调优主要是通过定制JVM参数来提Java应用程序的运行速度 JVM参数大致可以分为三类&#xff1a; &#xff11;、标准指令&#xff1a;&#xff0d;开头&#xff0c;这些是所有的HotSPot都支持的参数。可以用java -help打印出来&#xff1b; 2、非标准指令&#xff1a;-X开头…

Mysql高级篇(上)—— Mysql架构介绍(二)

Mysql高级篇&#xff08;上&#xff09; MySQL架构介绍&#xff08;二&#xff09;逻辑架构逻辑架构剖析MySQL8.0中SQL执行流程Linux环境下MySQL8.0中SQL执行原理MySQL语法顺序Oracle中SQL执行流程&#xff08;了解&#xff09;数据库缓存池 buffer pool&#xff08;了解&#…

86、pod部署策略

一、集群的调度 集群的调度&#xff1a; 怎么把pod部署到节点的方法。 1.1、调度的过程&#xff1a; scheduler是集群的调度器&#xff0c;主要任务就是把pod部署到节点上。 1.2、自动调度&#xff1a; 1、公平&#xff0c;保证每个可用的节点都可以部署pod 2、资源的高…

什么是工控安全,产线工控安全加固的方法

一、工控安全概述 想要了解工控安全&#xff0c;首先要了解其资产对象本身&#xff0c;也就是工业控制系统。 1、什么是工控 关于工业控制系统的定义&#xff0c;网上有很多&#xff0c;我就不再赘述&#xff0c;下面这张图是我从csdn找到的&#xff0c;个人觉得还不错。对照…

【多线程】CountDownLatch的简单实现

通过上一篇对CountDownLatch的使用&#xff0c;我们也明白了他的基本原理&#xff0c;接下来我们一起来实现一个CountDownLatch的基础效果 新建一个抽象类&#xff0c;包含countDownLatch需要的参数和方法 package com.atguigu.signcenter.nosafe.chouxiang;/*** author: jd* …

k8s集群的调度

目录 自动调度的原则 调度约束机制&#xff1a;list-watch apiserver和组件之间的watch机制 调度过程的默认算法 1.预算策略 预算的算法 2.优选策略 优选的算法 *用户定制节点部署 1.强制性节点部署 2.节点标签部署&#xff08;匹配机制&#xff09; 标签的增删改查…