HarmonyOS 鸿蒙Next中列表下拉刷新完数据,顶部数据不变,但是图片会重新加载从而闪烁一次,有什么方法可以不让图片闪烁吗?
HarmonyOS 鸿蒙Next中列表下拉刷新完数据,顶部数据不变,但是图片会重新加载从而闪烁一次,有什么方法可以不让图片闪烁吗? 列表下拉刷新完数据,顶部数据不变,但是图片会重新加载从而闪烁一次,有什么方法可以不让图片闪烁吗?
【背景知识】
- LazyForEach:根据提供的数据源按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。
- [@Observed装饰器和@ObjectLink装饰器](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-observed-and-objectlink):@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步。
- 设置图片缓存:使用图像加载缓存库ImageKnife设置图片缓存,或者配置LazyForEach懒加载的缓存最大容量和数量。
- [@Reusable组件复用](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-reusable):@Reusable表示组件可以被复用,结合LazyForEach懒加载一起使用,可以进一步解决列表滑动场景的瓶颈问题,提供滑动场景下高性能创建组件的方式来提升滑动帧率。
【解决方案】
- 场景一:使用错误键值触发组件重建(必要检查场景)
- 原因: 若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.希望楼主能够提供场景代码,方便实际分析
图片闪烁一版是组件重建导致图片重新加载,尤其在下拉刷新时,若更新方式不精准或缓存策略缺失,会导致顶部图片重新加载。
楼主试试下面办法:
使用 @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。
在HarmonyOS Next中,图片闪烁问题可通过以下方式解决:
- 使用
Image组件的syncLoad属性,设置为true实现同步加载。 - 为图片资源设置固定尺寸,避免布局重绘。
- 启用内存缓存,通过
Image的memoryCache属性配置。 - 考虑使用
LazyForEach优化列表项复用。
核心是控制图片加载策略与优化组件渲染。
在HarmonyOS Next中,图片在列表刷新时闪烁,通常是由于Image组件在数据更新时被重新创建和加载导致的。要解决这个问题,核心思路是避免不必要的图片重新加载。以下是几种有效的方案:
-
使用
cachedImage或PixelMap进行本地缓存cachedImage:这是最推荐的方式。它为网络图片提供了内存和磁盘二级缓存。当列表数据刷新但图片URL未改变时,组件会直接从缓存中读取图片数据,避免重新请求网络和渲染,从而消除闪烁。// 示例:使用cachedImage Image($r('app.media.placeholder')) // 占位图 .cachedImage(src, (img: image.Image) => { // 可选的图片处理回调 })PixelMap:如果你已经将图片数据加载为PixelMap对象,可以直接将其作为Image的源。由于PixelMap是内存中的图像数据句柄,复用它可以完全避免重复解码。// 假设 pixelMap 是已加载的 PixelMap 对象 Image(pixelMap) .width(...) .height(...)
-
为
Image组件设置id并启用reuseId- 在列表项中,为每个
Image组件设置一个唯一的、稳定的id(例如,基于图片URL或条目ID生成)。 - 启用
reuseId属性,系统会尝试复用相同id的组件节点,这有助于保持图片的渲染状态,避免整个图片节点的重建。Image(src) .id(this.getItemImageId(item)) // 为每个图片项设置稳定id .reuseId(true) // 启用组件复用
- 在列表项中,为每个
-
优化列表数据更新策略
- 确保你的数据更新逻辑是精确的。如果列表顶部的数据确实没有变化,在触发刷新时,应避免生成全新的数组或对象,而是尽量复用原有的数据对象引用。ArkUI的UI更新机制依赖于状态变量的变化检测,直接替换整个数组会导致所有列表项都被认为是新的。
- 考虑使用更精细的状态管理,例如,只更新真正发生变化的那部分数据对应的状态变量。
-
利用
List组件的cachedCount属性- 增加
List的cachedCount值(例如设置为-1表示缓存所有项)。这会使更多列表项在离开可视区后保持活跃状态,当它们再次进入可视区时,恢复速度更快,可能减少图片重新加载的感知。
- 增加
总结与首选方案:
对于由网络图片引起的闪烁,方案1(使用 cachedImage)是最直接、高效的解决方案。它从加载源头解决了重复请求和渲染的问题。如果图片是本地资源或已处理好的 PixelMap,则直接使用 PixelMap 源。
同时,可以结合方案2,为 Image 设置稳定的 id 并开启 reuseId,为系统级的组件复用提供提示。
检查并优化数据更新逻辑(方案3)是根本性措施,能从数据驱动层面减少不必要的UI刷新。方案4作为辅助手段,可以根据实际列表的复杂度和性能要求进行调整。


