item gone
在 onBindViewHolder 中设置 holder.itemView gone 是无效
的:虽然不会显示 item 的内容,但该占的位置还是占住了,类似 INVISIBLE 效果
原因么,就是 rv 在 layout 各个 item 时没有将 gone item 排除在外。以 LinearLayoutManager 为例看一下源码:
// LinearLayoutManager
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// 通过缓存取下一个要显示的 view。涉及到 onCreateVH,onBindVH 之类的
View view = layoutState.next(recycler);
// 对 view 发起 measure 过程
measureChildWithMargins(view, 0, 0);
// 获取当前 view 一共消耗了多少空间。以垂直为例:
// 返回的是 item 高度 + 垂直 margin + ItemDecoration#getItemOffsets 设置的垂直值
// 从这里可以看出,无论 view 有没有 gone,rv 都会把它应占据的空间给留出来
// 可以与 LinearLayout 对比看:LinearLayout 在 layout 子 view 时会判断子 view 是不是 gone
// 神奇的一逼,不知道为啥这么处理
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
// .....
// 发起 layout 过程
layoutDecoratedWithMargins(view, left, top, right, bottom);
}
解决办法:将影响高度的三个因素都修改成 0 即可
。
notifyDataSetChanged 与 notifyItemChanged
- notifyDataSetChanged:
所有 item 都会放到 pool 缓存中
(如果能放得下的话),所以 vh 会复用但需要重新执行 onBindViewHolder - 后者,影响范围内的会放到 changeScrap 中,其余的会放到 attachScrap 中。这里说一下两个 scrap 的区别:
change 只在预布局时使用,其中缓存的 vh 最终会进到 pool 中
两个方法最终都会调用 requestLayout,从而会执行 layout 过程,然后就会到 LayoutManager#onLayoutChildren,该方法会调用 detachAndScrapAttachedViews(),该方法会倒序遍历所有 child,对它们进行回收
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
// RV 本身是 ViewGroup
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
// 这里的 If 判断就是 notifyDataSetChanged 与 notifyItemChanged 的分歧
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
// notifyDataSetChanged 走这里,最终 vh 会加到 pool 缓存中
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
// 这里会将 vh 加到 mAttachedScrap 或者 mChangedScrap
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
上面就是两个方法导致 vh 回收时的逻辑。再结合 vh 的复用,就可以得出两者的区别。缓存的使用逻辑如下:onLayoutChildren() 会调用到 layoutChunk(),最终到 RecyclerView.Recycler#next() 拿到 vh(有可能是新建,也有可能是从缓存中拿),具体代码分析省略。