RecyclerView局部刷新机制

article/2025/10/18 9:52:14

之前在使用RecyclerView的遇到过一个问题,使用notifyItemChanged刷新数据的时候会出现重影或者闪烁的现象。

这个问题很容易出现,当我们的列表中有进度显示(比如下载),这时候需要不停的更新进度,就需要使用notifyItemChanged

使用notifyItemChanged可以只刷新那一个item,这样就避免了像ListView那样全部刷新

但是如果使用notifyItemChanged(position),在滑动的时候刷新就会出现重影或者闪烁的问题。

解决这个问题很简单,将notifyItemChanged(position)替换为notifyItemChanged(position,0)即可。

测试问题确实解决了,但是为啥?这个参数有啥用?

源码分析

我们从源码入手来看看

public final void notifyItemChanged(int position, @Nullable Object payload) {this.mObservable.notifyItemRangeChanged(position, 1, payload);
}

可以看到payload是一个object,并非int。它调用了mObservable的notifyItemRangeChanged

public void notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {for(int i = this.mObservers.size() - 1; i >= 0; --i) {((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onItemRangeChanged(positionStart, itemCount, payload);}}

调用了AdapterDataObserver的onItemRangeChanged,这是一个接口,它的实现是RecyclerViewDataObserver,实现的函数

public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {RecyclerView.this.assertNotInLayoutOrScroll((String)null);if(RecyclerView.this.mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {this.triggerUpdateProcessor();}
}

又调用了mAdapterHelper的onItemRangeChanged

boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {if(itemCount < 1) {return false;} else {this.mPendingUpdates.add(this.obtainUpdateOp(4, positionStart, itemCount, payload));this.mExistingUpdateTypes |= 4;return this.mPendingUpdates.size() == 1;}
}

调用了obtainUpdateOp函数

public AdapterHelper.UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload) {AdapterHelper.UpdateOp op = (AdapterHelper.UpdateOp)this.mUpdateOpPool.acquire();if(op == null) {op = new AdapterHelper.UpdateOp(cmd, positionStart, itemCount, payload);} else {op.cmd = cmd;op.positionStart = positionStart;op.itemCount = itemCount;op.payload = payload;}return op;
}

可以看到作为参数赋给一个UpdateOp对象,那么哪里使用了这个对象的payload?

在AdapterHelper中查找发现有几处这样的代码

this.mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);

这个callback也是一个接口,在RecyclerView中可以找到它的实现,其中对应的函数:

public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {RecyclerView.this.viewRangeUpdate(positionStart, itemCount, payload);RecyclerView.this.mItemsChanged = true;
}

调用了viewRangeUpdate函数

void viewRangeUpdate(int positionStart, int itemCount, Object payload) {int childCount = this.mChildHelper.getUnfilteredChildCount();int positionEnd = positionStart + itemCount;for(int i = 0; i < childCount; ++i) {View child = this.mChildHelper.getUnfilteredChildAt(i);RecyclerView.ViewHolder holder = getChildViewHolderInt(child);if(holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart && holder.mPosition < positionEnd) {holder.addFlags(2);holder.addChangePayload(payload);((RecyclerView.LayoutParams)child.getLayoutParams()).mInsetsDirty = true;}}this.mRecycler.viewRangeUpdate(positionStart, itemCount);
}

可以看到调用了holder的addChangePayload

void addChangePayload(Object payload) {if(payload == null) {this.addFlags(1024);} else if((this.mFlags & 1024) == 0) {this.createPayloadsIfNeeded();this.mPayloads.add(payload);}}private void createPayloadsIfNeeded() {if(this.mPayloads == null) {this.mPayloads = new ArrayList();this.mUnmodifiedPayloads = Collections.unmodifiableList(this.mPayloads);}}List<Object> getUnmodifiedPayloads() {return (this.mFlags & 1024) == 0?(this.mPayloads != null && this.mPayloads.size() != 0?this.mUnmodifiedPayloads:FULLUPDATE_PAYLOADS):FULLUPDATE_PAYLOADS;
}

这里有两个list,mPayloads和mUnmodifiedPayloads,在getUnmodifiedPayloads中可以看到当mPayloads不为空才会返回mUnmodifiedPayloads,否则返回FULLUPDATE_PAYLOADS,即Collections.EMPTY_LIST。

在RecyclerView中搜索getUnmodifiedPayloads函数,发现其中一处应该跟我们的问题有关

boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {return this.mItemAnimator == null || this.mItemAnimator.canReuseUpdatedViewHolder(viewHolder, viewHolder.getUnmodifiedPayloads());
}

payloads应该对这个函数的返回值有影响,继续看mItemAnimator的对应函数。

这个mItemAnimator也是一个接口,实现类是DefaultItemAnimator,它的对应函数

public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, @NonNull List<Object> payloads) {return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
}

可以看到如果payloads不为空,即最开始的payload不为null(因为是object,所以0还是其它都无所谓,只要不为空就行),canReuseUpdatedViewHolder则为true。
那么canReuseUpdatedViewHolder影响什么,同样在RecyclerView中搜索发现

void scrapView(View view) {RecyclerView.ViewHolder holder = RecyclerView.getChildViewHolderInt(view);if(!holder.hasAnyOfTheFlags(12) && holder.isUpdated() && !RecyclerView.this.canReuseUpdatedViewHolder(holder)) {if(this.mChangedScrap == null) {this.mChangedScrap = new ArrayList();}holder.setScrapContainer(this, true);this.mChangedScrap.add(holder);} else {if(holder.isInvalid() && !holder.isRemoved() && !RecyclerView.this.mAdapter.hasStableIds()) {throw new IllegalArgumentException("Called scrap view with an invalid view. Invalid views cannot be reused from scrap, they should rebound from recycler pool." + RecyclerView.this.exceptionLabel());}holder.setScrapContainer(this, false);this.mAttachedScrap.add(holder);}}

可以看到如果有payload,holder会被放入mAttachedScrap,否则放入mChangedScrap。

mAttachedScrap和mChangedScrap

这两个就涉及到RecyclerView的缓存机制了,整个缓存机制包含多个集合,这两个集合就是其中的重要部分,这个机制就不在这篇文章里细说了。

先看看它们俩个有什么用

简单来说当holder有了变化就会放入mChangedScrap,这样刷新的时候会移除重新bind一下;

而holder没有改变则放入mAttachedScrap,这样刷新的时候就不需要重新bind,直接更新数据即可。

所以正是因为没有payload需要重新bind,所以会出现闪烁。而在滑动中不仅位置一直变,因为进度也在变,所以不停的进行移除bind,就会导致重影的现象。

而使用了payload后,不会移除重新bind,只更新进度条自己,就不会闪烁或重影了。

payload的大用处

最后再补充一个重要的部分!

payload的应用不仅仅是这么简单,在研究的过程中我还发现了另外一个函数

public void onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads) {this.onBindViewHolder(holder, position);
}

很熟悉吧,这是RecyclerView的Adapter中的一个函数,我们一般使用

public abstract void onBindViewHolder(@NonNull VH var1, int var2);

因为上面那个重载的函数不是abstract的,所以我们不容易注意到。那么这个函数有什么用?

可以看到默认处理就是调用了下面的函数,没什么特殊,但是我们可以重写它。

比如说我们刷新的时候,只想改变一个TextView的文案
如果是之前的处理,会重新执行一遍onBindViewHolder(@NonNull VH var1, int var2),这样不仅那个TextView,其它组件也会更新一遍数据,虽然数据没变,尤其有图片的时候需要重新load一次。

但是重写onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads)我们就可以只为TextView重新设置文案即可,如下:

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {if(payloads.isEmpty()){onBindViewHolder(holder, position);}else{holder.tv.setText("change text");}
}

而且通过对payload设置不同的值,我们可以通过判断payload分别处理不同的刷新,比如:

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {if(payloads.isEmpty()){onBindViewHolder(holder, position);}else{for(Object obj : payloads){if((int)obj == 1){holder.tv.setText("change text");}else if((int)obj == 2){holder.img.setImageBitmap(newBitmap);}}}
}

所以payload再配合onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads)使用就可以实现RecyclerView的item的局部刷新,不用再刷新整条item了。

总结

payload机制作用很大,尤其是当RecyclerView中的每个Item布局和数据比较复杂,需要单独更新的时候。使用payload不仅仅解决闪烁和重影问题,也会使更新更高效,减少资源开销。

 


http://chatgpt.dhexx.cn/article/GAfuTgSZ.shtml

相关文章

jsp java局部刷新_jsp怎么实现局部刷新

jsp实现局部刷新的方法&#xff1a;首先创建一个处理ajax请求的jsp文件&#xff1b;然后设置输出信息的格式及字符集&#xff1b;最后利用JSP和ajax来实现局部页面刷新即可。 通过 AJAX&#xff0c;JavaScript 可使用 JavaScript 的 XMLHttpRequest 对象来直接与服务器进行通信…

bootstrap切换tab页局部刷新_AdminLTE实现局部刷新

前言 AdminLTE是一个基于boostrap的前端模板,里面集成了好多插件,可以说方便又臃肿,毕竟不是所有插件都用得到,。好不容易找到个喜欢的前端模板,无奈每次点击菜单都会整个页面刷新一次,网上找了半天也没找到一个喜欢的局部刷新的解决方法。只好自己去啃js了。由于修改了原…

java局部刷新_HTML页面局部刷新的实现代码

这篇文章主要介绍了HTML页面局部刷新的实现代码的相关资料&#xff0c;写的十分的全面细致&#xff0c;具有一定的参考价值&#xff0c;对此有需要的朋友可以参考学习下。如有不足之处&#xff0c;欢迎批评指正。 事件响应刷新&#xff1a;有请求才会刷新 1、通过JS HTML DOM或…

原生JS局部刷新

目录 使用XMLHttpRequest对象进行异步请求&#xff1a; 2.使用fetch API进行异步请求 3.使用事件监听器进行局部刷新 4.servlet实现img验证码局部刷新 依赖jar包 Servlet login.jsp 在原生JS中&#xff0c;可以使用以下几种方式实现局部刷新&#xff1a; 使用XMLHttpReques…

html局部刷新数据,局部刷新.html

&#xfeff;局部刷新 $axure.utils.getTransparentGifPath function() { return resources/images/transparent.gif; }; $axure.utils.getOtherPath function() { return resources/Other.html; }; $axure.utils.getReloadPath function() { return resources/reload.html;…

flutter 局部刷新

目的&#xff1a;局部刷新 效果&#xff1a;点击右下角刷新按钮后&#xff0c;对九宫格中的图片刷新状态 思路&#xff1a;两个方法 一、整个页面都刷新&#xff0c;局部组件有变化&#xff0c;用UniqueKey() 二、只针对局部组件刷新&#xff0c;用GlobalKey() 具体操作&…

什么是局部刷新

局部刷新 浏览器在展示数据时&#xff0c;此时在窗口既可以看到本次的响应数据&#xff0c;同时又可以看到浏览器内存原有数据。 局部刷新原理&#xff1a; 不由浏览器发送请求给服务端 浏览器委托浏览器内存中一个脚本对象代替浏览器发送请求 这个行为导致服务端直接将…

【PSFTP】Windows从Linux获取文件或目录

1、安装Putty Win10先安装Putty 官方下载地址&#xff1a;http://www.putty.be/latest.html 安装后&#xff0c;Win10运行PSFTP 2、登录Linux 提示使用open host.name连接服务器 psftp: no hostname specified; use "open host.name" to connect psftp>参考…

putty、pscp、psftp 使用教程

如何从安装了Windows的工作电脑连远程接到Linux服务器?其实有很多软件,比如 PuTTY、XShell、CRT、MobaXterm等等。不过还是 PuTTY最简单易用、无需安装、并且开源免费。PuTTY其实是一个软件套装,里边除了最常用的putty之外,还包含了像 pscp、psftp等可以用于文件传输的工具…

putty和psftp命令行参数

putty和psftp命令行参数 https://the.earth.li/~sgtatham/putty/latest/w32/putty.zip https://the.earth.li/~sgtatham/putty/latest/w64/putty.zip https://the.earth.li/~sgtatham/putty/latest/puttydoc.zip https://the.earth.li/~sgtatham/putty/latest/putty-0.72.tar.g…

psftp

2019独角兽企业重金招聘Python工程师标准>>> 当连接到远程计算机以后&#xff0c;使用以下命令&#xff1a; bye 结束 psftp 。 cd 改变远程服务器的目录。 chmod 改变远程服务器的文件或文件夹的权限及属性。 del 删除远程服务器上的文件。 dir …

linux psftp,使用PSFTP实现Windows、Linux之间的文件传输

安装PuTTY时自动安装了PSFTP 使用PSFTP可以实现Winodws、Linux之间的文件传输。 打开PSFTP&#xff0c;输入Linux的ip地址&#xff0c;输入要登录的用户名、密码 Windows向Linux传文件&#xff1a; put D:\jdk-8u241-linux-x64.rpm /root/jdk-8u241-linux-x64.rpm put 本地文件…

putty以及psftp的基本操作,使用方法等

1、putty登陆远程服务器 open之后进入登陆界面&#xff0c;输入用户名之后点击Enter&#xff0c;之后输入登陆密码&#xff08;界面不显示&#xff0c;输入正确后直接Enter就可以&#xff09; 进入之后的界面 之后就可以输入命令进行操作了 2、文件传输psftp&#xff1a; 运行…

PSFTP工具的使用教程

PSFTP&#xff1a;是Putty的SFTP客户端&#xff0c;可以通过SFTP协议在两台电脑之间的传输文件。它和 PSCP相比的优点在于可以与服务器进行交互&#xff0c;遍历服务器上的文件系统&#xff0c;在一个会话中上传或下载多个文件。而 PSCP 只能一次传输一个文件&#xff0c;传输完…

Vector3.Lerp

Unity3D中的线性插值Lerp()函数解析 在unity3D中经常用线性插值函数Lerp()来在两者之间插值&#xff0c;两者之间可以是两个材质之间、两个向量之间、两个浮点数之间、两个颜色之间&#xff0c;其函数原型如下&#xff1a; Material.Lerp 插值 function Lerp(start : Materi…

Unity 的Vector3.Project 和 Vector3.ProjectOnPlane

目录 1.public static Vector3 Project(Vector3 vector, Vector3 onNormal); 描述 &#xff1a; 代码&#xff1a; 效果&#xff1a; 结论&#xff1a; 2.public static Vector3 ProjectOnPlane(Vector3 vector, Vector3 planeNormal);返回向量在平面上的位置。 描述 …

Vector3——简单的3D向量类

参考资料&#xff1a;1、 [美] 邓恩&#xff08;Dunn F.&#xff09;著. 3D数学基础——图形设计与开发. 史银雪&#xff0c;陈洪&#xff0c;王荣静 译 清华大学出版社 p57-65 2、http://www.2cto.com/kf/201311/260139.html 编程环境 QT4.8.4 VS2010 本文用 C实现一个简单…

Unity Vector3.Dot(VectorA, VectorB)

Unity Vector3.Dot(VectorA, VectorB) Vector3.Dot(VectorA, VectorB) 等于 VectorA * VectorB。 而对于两个向量的乘积计算&#xff1a; VectorA * VectorB Ax * Bx Ay * By Az * Bz 例如&#xff1a; VectorA(1,2,3) * VectorB(4,5,6) 1*42*53*632 一般应用于判断飞行…

Unity - 优化 Vector3.ProjectOnPlane

文章目录 起因好奇绘制 Vector3.ProjectOnPlane 的参数&#xff0c;与返回结果绘制结果定位作用、优化完整脚本ProjectReferences 起因 之前偶然项目中看到过 API Vector3.ProjectOnPlane API 的使用 然在这篇文章也看到&#xff1a;Unity3d那些你不常用但确有其用的方法—Ve…

Unity —— Vector3

Vector3向量&#xff0c;在三维坐标系中带有方向和大小的数据 Vector3中一些常量&#xff1a; 1、Vector3.back&#xff08;0,0,-1&#xff09; 2、Vector3.forward&#xff08;0,0,1&#xff09; 3、Vector3.left&#xff08;-1,0,0&#xff09; 4、Vector3.right&#xff0…