HarmonyOS 鸿蒙Next中列表下拉刷新完数据,顶部数据不变,但是图片会重新加载从而闪烁一次,有什么方法可以不让图片闪烁吗?

HarmonyOS 鸿蒙Next中列表下拉刷新完数据,顶部数据不变,但是图片会重新加载从而闪烁一次,有什么方法可以不让图片闪烁吗? 列表下拉刷新完数据,顶部数据不变,但是图片会重新加载从而闪烁一次,有什么方法可以不让图片闪烁吗?

8 回复

【背景知识】

【解决方案】

  • 场景一:使用错误键值触发组件重建(必要检查场景)
    • 原因: 若LazyForEach使用了错误的键值(键值重复、键值跟随UI变化),当键值变化时,ArkUI框架将视为该数组元素已被替换或修改,触发组件重建。
    • 典型场景: 交换列表项后,两个图片短暂消失并重新加载,出现闪烁。
    • 错误代码示例:
      LazyForEach(this.data, (item) => { ... }, (item) => "same key")  // key相同
      LazyForEach(this.data, (item) => { ... }, (item) => item.index)  // index在前置代码中进行了变化
      
    • 修改方式: 改用唯一标识符。为每个数据项定义唯一ID,作为keyGenerator的返回值,确保组件复用稳定性。
    • 核心代码:
      // 数据列表中有唯一主键id
      LazyForEach(this.data, (item) => { ... }, (item) => item.id)  // 以item.id为key
      
  • 场景二:组件重建触发图片重载
    • 原因: 当数据源变更时,LazyForEach默认会销毁旧组件并重建新组件,导致图片重新加载。
    • 典型场景:
      • 增删改列表图文项,所有已有图片短暂消失再重新加载,出现闪烁;
      • 仅修改列表中图文项的文字信息,图片会出现闪烁。(直接更改列表项中的文本部分,并通过notifyDataChange或notifyDataReload进行通知,都仍然会造成同行图片闪烁问题,因为该方式会直接修改数据源,造成组件重新加载。)
      • 修改方式: 使用@Observed+@ObjectLink进行数据项的精准更新。
      • 核心代码: 对数据类添加@Observed装饰器,子组件通过@ObjectLink绑定需响应的属性,仅更新变化部分。下方代码在修改message时,仅会触发Text更新,Image将保持原组件状态。详细代码请见:重渲染时图片闪烁
  • 场景三:图片加载未缓存导致短暂空白
    • 原因: 图片加载需从资源或网络获取,若未启用缓存机制,重复加载会导致短暂空白或闪烁。
    • 典型场景: 列表实时加载网络图片,来回滚动列表时,图片出现闪烁。
    • 修改方式: 使用ImageKnife预加载图片时,这张图片会被缓存到内存当中,只要在它还没从内存中被清除之前,下次再加载这张图片都会直接从内存中读取,而不用重新从网络或硬盘上读取,大幅度提升图片的加载效率,确保图片展示时不会出现因加载导致的短暂空白。除此之外,若自动的内存缓存不能满足处理图片的需求,开发者还可以在RequestOption类中配置相应的参数,包括图片路径、图片大小、占位图及缓存策略等。
  • 场景四:数据量大时内存占用过大,渲染卡顿
    • 原因: 数据量极大,滑出可视区的数据不断重建,造成渲染卡顿、滑动丢帧。
    • 典型场景: 长列表滚动卡顿,图片闪烁。
    • 修改方式: 启用组件复用与缓存,通过@Reusable装饰器标记可复用列表项组件,滑出可视区后存入复用池,减少重建开销,详细原理请见:组件复用;同时配置缓冲区,预加载可视区前后N个项,降低滑动时的白屏概率,详细原理请见:缓存列表项
    • 核心代码:在缓存列表项的基础上增加组件复用

【常见FAQ】 Q:@ObjectLink@Observed修饰的数据更新后界面并未刷新。 A:请参考[@ObjectLink@Observed的详细使用方法和限制条件](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-observed-and-objectlink)。

Q:列表滚动配合LazyForEach中如何使用组件复用? A:请参考指南:列表滚动配合LazyForEach使用

Q:在使用@Reusable降低渲染卡顿、滑动丢帧的问题时,随着数据的增加,内存也在一直增长,直到触发到回收。 A:可以通过提高组件的复用率,来降低内存的增速:使用aboutToRecycle回调实现节点的复用,或者设置freezeWhenInactive属性为true,启用自定义组件冻结功能。

Q:使用swiper和lazyForeach展示列表数据,当动态添加数据时item会闪烁,添加数据的代码如下:

export class  WaterfallFlowDataSource extends BasicDataSource {
  mediaArray:Array<MediaBean> = []
  ...
  public putArrayData(data:Array<MediaBean>){
    let length = data.length
    this.mediaArray = this.mediaArray.concat(data)
    this.notifyDatasetChange([
      {
        type:DataOperationType.ADD,index:length == 0?0:length - 1,count:data.length
      }
    ])
  }
}

A:notifyDatasetChange会直接修改数据源,造成组件重新加载,可以使用notifyDataAdd通知控制器数据增加。

export class  WaterfallFlowDataSource extends BasicDataSource {
  mediaArray:Array<MediaBean> = []
  ...
  public putArrayData(data:Array<MediaBean>){
    this.mediaArray = this.mediaArray.concat(data)
    this.notifyDataAdd(this.mediaArray.length - 1)
  }
}

Q:使用被@ObjectLink@Observed修饰的数据发生变动时,图片刷新后仍然出现闪烁现象,其原因是什么? A:@Observed类装饰器,需要放在class的定义前,使用new创建类对象,参考链接:[@Observed类装饰器](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-observed-and-objectlink)。不使用new创建类也会导致刷新时出现闪烁。

【总结】 LazyForEach触发图片闪烁的核心在于组件重建,通过精准更新数据源(@Observed/@ObjectLink)、启用缓存策略(图片与组件复用),可显著减少闪烁问题。对于复杂场景,建议结合@Reusable和cachedCount进一步优化性能。

问题场景 优化手段
①拖拽交换列表项顺序;②列表数据重新排序(如按时间倒序排列);③动态增删列表项(如删除聊天消息列表中的某一项)。 key值唯一且固定(必备条件)
①点击按钮后批量替换数据源;②局部属性变更(如文本、状态)错误。 配套使用@Observed/@ObjectLink精准更新
①首次进入页面时加载图片;②频繁切换图片URL;③较多高清大图。 预加载图片
长列表滑动 @Reusable标记可复用组件并配置缓冲区

更多关于HarmonyOS 鸿蒙Next中列表下拉刷新完数据,顶部数据不变,但是图片会重新加载从而闪烁一次,有什么方法可以不让图片闪烁吗?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


1.楼主可以参考一下这个FAQ是否和你说的场景一样
页面加载时,组件出现异常闪烁-行业常见问题-旅游园区类行业实践-行业实践 - 华为HarmonyOS开发者

是的话可以考虑增加骨架屏

@Entry
@Component
struct Index {
  @State translageX: string = '-100%';
  widthValue: number = 100;
  heightValue: number = 28;

  build() {
    Stack() {
      // 背景
      Text()
        .height(this.heightValue)
        .width(this.widthValue)
        .backgroundColor('rgba(220,220,220,1)')
      // 动画
      Text()
        .height(this.heightValue)
        .width(this.widthValue)
        .translate({ x: this.translageX })
        .onAppear(() => {
          this.translageX = '100%';
        })
        .animation({
          duration: 1500,
          iterations: -1,
        })
        .linearGradient({
          angle: 90,
          colors: [
            ['rgba(255,255,255,0)', 0],
            ['rgba(255,255,255,1)', 0.5],
            ['rgba(255,255,255,0)', 1]
          ]
        })
      }
    }
  }
}

2.希望楼主能够提供场景代码,方便实际分析

图片闪烁通常是因为组件被重新创建,导致图片重新加载

使用 @State@Link 优化数据更新,避免整个组件的重建

@Entry
@Component

struct MyList {
  [@State](/user/State) listData: Array<ItemData> = [];
  [@State](/user/State) refreshing: boolean = false;

  build() {
    Refresh({ refreshing: this.refreshing, offset: 120, friction: 100 }) {
      List() {
        ForEach(this.listData, (item: ItemData) => {
          ListItem({ item: item })
        })
      }

    } .onRefreshing(() => {
      this.refreshData();
    })
  }

  async refreshData() {
    this.refreshing = true;

    // 获取新数据
    const newData = await this.fetchData();

    // 使用动画更新数据,避免闪烁
    animateTo({ duration: 300 }, () => {
      this.listData = newData;
    });

    this.refreshing = false;
  }
}

参考地址

https://developer.huawei.com/consumer/cn/doc/architecture-guides/office-v1_2-ts_13-0000002319725450

图片闪烁一版是组件重建导致图片重新加载,尤其在下拉刷新时,若更新方式不精准或缓存策略缺失,会导致顶部图片重新加载。

楼主试试下面办法:

使用 @Observed + @ObjectLink 绑定数据,仅更新变动部分,避免组件重建:

// 数据类添加[@Observed](/user/Observed)装饰器
[@Observed](/user/Observed)
export class ItemData {
  id: string;
  imageUrl: string;
  // 其他属性...
}

// 子组件通过[@ObjectLink](/user/ObjectLink)绑定
@Component
struct ListItem {
  [@ObjectLink](/user/ObjectLink) item: ItemData;
  build() {
    Image(this.item.imageUrl)
      .cachedCount(5) // 启用图片缓存
  }
}

使用ImageKnife配置内存缓存,减少重复加载:

ImageKnife.setDefaultOptions({
  placeholder: $r('app.media.loading'),
  error: $r('app.media.error'),
  // 启用内存缓存
  memoryCacheOptions: {
    maxSize: 50, // MB
    strategy: CacheStrategy.ForceCache
  }
});

为列表项定义唯一标识符,避免使用index作为Key,这样可以保证组件复用稳定性,减少重建概率:

LazyForEach(this.dataSource, 
  (item: ItemData) => item.id, // 唯一ID作为Key
  (item: ItemData) => {
    ListItem({ item })
  }
)

刷新时避免清空数据源,仅追加或修改局部数据:

//增量更新
this.dataSource.addOrUpdateItems(newData);
this.controller.notifyDataAdd(startIndex, newData.length);

只改变了数据项的message属性,但LazyForEach的刷新机制会导致整个ListItem被重建。由于Image组件异步刷新,视觉上图片会闪烁。解决方法是使用@ObjectLink@Observed单独刷新子组件Text。

以下是官方提供的解决方案:

1、长列表数据刷新闪烁

2、重渲染时图片闪烁

在HarmonyOS Next中,图片闪烁问题可通过以下方式解决:

  1. 使用Image组件的syncLoad属性,设置为true实现同步加载。
  2. 为图片资源设置固定尺寸,避免布局重绘。
  3. 启用内存缓存,通过ImagememoryCache属性配置。
  4. 考虑使用LazyForEach优化列表项复用。

核心是控制图片加载策略与优化组件渲染。

在HarmonyOS Next中,图片在列表刷新时闪烁,通常是由于Image组件在数据更新时被重新创建和加载导致的。要解决这个问题,核心思路是避免不必要的图片重新加载。以下是几种有效的方案:

  1. 使用 cachedImagePixelMap 进行本地缓存

    • cachedImage:这是最推荐的方式。它为网络图片提供了内存和磁盘二级缓存。当列表数据刷新但图片URL未改变时,组件会直接从缓存中读取图片数据,避免重新请求网络和渲染,从而消除闪烁。
      // 示例:使用cachedImage
      Image($r('app.media.placeholder')) // 占位图
        .cachedImage(src, (img: image.Image) => {
          // 可选的图片处理回调
        })
      
    • PixelMap:如果你已经将图片数据加载为 PixelMap 对象,可以直接将其作为 Image 的源。由于 PixelMap 是内存中的图像数据句柄,复用它可以完全避免重复解码。
      // 假设 pixelMap 是已加载的 PixelMap 对象
      Image(pixelMap)
        .width(...)
        .height(...)
      
  2. Image 组件设置 id 并启用 reuseId

    • 在列表项中,为每个 Image 组件设置一个唯一的、稳定的 id(例如,基于图片URL或条目ID生成)。
    • 启用 reuseId 属性,系统会尝试复用相同 id 的组件节点,这有助于保持图片的渲染状态,避免整个图片节点的重建。
      Image(src)
        .id(this.getItemImageId(item)) // 为每个图片项设置稳定id
        .reuseId(true) // 启用组件复用
      
  3. 优化列表数据更新策略

    • 确保你的数据更新逻辑是精确的。如果列表顶部的数据确实没有变化,在触发刷新时,应避免生成全新的数组或对象,而是尽量复用原有的数据对象引用。ArkUI的UI更新机制依赖于状态变量的变化检测,直接替换整个数组会导致所有列表项都被认为是新的。
    • 考虑使用更精细的状态管理,例如,只更新真正发生变化的那部分数据对应的状态变量。
  4. 利用 List 组件的 cachedCount 属性

    • 增加 ListcachedCount 值(例如设置为 -1 表示缓存所有项)。这会使更多列表项在离开可视区后保持活跃状态,当它们再次进入可视区时,恢复速度更快,可能减少图片重新加载的感知。

总结与首选方案: 对于由网络图片引起的闪烁,方案1(使用 cachedImage)是最直接、高效的解决方案。它从加载源头解决了重复请求和渲染的问题。如果图片是本地资源或已处理好的 PixelMap,则直接使用 PixelMap 源。

同时,可以结合方案2,为 Image 设置稳定的 id 并开启 reuseId,为系统级的组件复用提供提示。

检查并优化数据更新逻辑(方案3)是根本性措施,能从数据驱动层面减少不必要的UI刷新。方案4作为辅助手段,可以根据实际列表的复杂度和性能要求进行调整。

回到顶部