如何更新RecyclerView隐藏项?现有方案存在性能损耗问题
哥们,你现在的问题核心在于:你试图通过遍历RecyclerView的可见子View来定位要更新的项,但RecyclerView的复用机制决定了——不可见的项要么已经被回收进复用池,要么还没被创建,所以你的代码根本找不到这些隐藏项。禁用复用虽然能解决,但代价是滑动流畅性和内存占用,这显然不是长久之计。
正确的思路应该是从数据源和Adapter的itemId机制入手,完全不需要直接操作View,这才是RecyclerView的正确打开方式。下面给你具体的解决方案:
1. 先给Adapter配置唯一的itemId
首先,在你的RecyclerView.Adapter中重写getItemId()方法,让每个item返回对应的唯一itemId(就是你用来做标签的那个id):
@Override public long getItemId(int position) { // 假设你的数据模型是DataItem,里面有getUniqueId()方法返回你的itemId return mDataList.get(position).getUniqueId(); }
同时,记得在初始化Adapter时调用setHasStableIds(true),这样RecyclerView会记住每个item的唯一标识,复用的时候能正确匹配。
2. 直接通过数据源定位并更新
当后台事件触发更新时,你不需要去管View是否可见,只需要在数据源中找到对应itemId的位置,然后调用Adapter的notify方法即可:
方案A:兼容所有API版本
遍历你的数据源列表,找到目标item的position,然后调用notifyItemChanged(int position):
long targetItemId = ...; // 后台传来的需要更新的itemId List<DataItem> dataList = mAdapter.getDataList(); // 假设Adapter提供获取数据源的方法 for (int i = 0; i < dataList.size(); i++) { if (dataList.get(i).getUniqueId() == targetItemId) { // 先更新数据源里的对应数据(这一步很重要!不然刷新后还是旧数据) dataList.set(i, updatedDataItem); // 通知Adapter更新该位置 mAdapter.notifyItemChanged(i); break; } }
方案B:API 24+ 简化写法
如果你的最低兼容版本是Android 7.0(API 24)及以上,可以直接用notifyItemChanged(long itemId),不用自己遍历找position:
long targetItemId = ...; // 先更新数据源里的对应数据 updateDataInList(targetItemId, updatedDataItem); // 直接通过itemId通知更新 mAdapter.notifyItemChanged(targetItemId);
3. 优化小技巧:用HashMap提升查找效率
如果你的数据源很大,遍历查找position的效率会很低,可以维护一个HashMap<Long, Integer>来存储itemId和对应position的映射关系:
private HashMap<Long, Integer> mItemIdToPositionMap = new HashMap<>(); // 初始化或数据源变化时更新映射 private void updateItemIdMap() { mItemIdToPositionMap.clear(); for (int i = 0; i < mDataList.size(); i++) { mItemIdToPositionMap.put(mDataList.get(i).getUniqueId(), i); } } // 更新时直接通过Map获取position Integer targetPosition = mItemIdToPositionMap.get(targetItemId); if (targetPosition != null) { mDataList.set(targetPosition, updatedDataItem); mAdapter.notifyItemChanged(targetPosition); }
注意:每次数据源发生添加、删除、排序变化时,都要同步更新这个HashMap,否则会出现位置不匹配的问题。
为什么原来的方法不行?
RecyclerView的核心就是视图复用,它只会在内存中保留当前可见的View(加上少量预加载的),不可见的View会被回收,或者还没被创建。你遍历getChildCount()只能拿到当前屏幕上的View,自然找不到隐藏项。而setIsRecyclable(false)会让每个item都保留独立的View,完全放弃了复用,内存占用会飙升,滑动自然就卡了。
内容的提问来源于stack exchange,提问作者RiccardoCh

