RecyclerView是一种高度可定制的View控件,它可以用于显示大量的数据集合,用一种更有效的方式来管理数据的展示和滚动。
implementation 'androidx.recyclerview:recyclerview:1.1.0' //recyclerview布局
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" />
RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(this));
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { private List<String> mData; public MyAdapter(List<String> data) { mData = data; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder, int position) { holder.mTextView.setText(mData.get(position)); } @Override public int getItemCount() { return mData.size(); } public static class ViewHolder extends RecyclerView.ViewHolder { private TextView mTextView; public ViewHolder(View itemView) { super(itemView); mTextView = (TextView) itemView.findViewById(R.id.text_view); } } }
RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(this)); MyAdapter adapter = new MyAdapter(data); recyclerView.setAdapter(adapter);
void scrapView(View view) { final ViewHolder holder = getChildViewHolderInt(view); if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { if (holder.isInvalid() && !holder.isRemoved() && !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." + exceptionLabel()); } holder.setScrapContainer(this, false); mAttachedScrap.add(holder); } else { if (mChangedScrap == null) { mChangedScrap = new ArrayList<ViewHolder>(); } holder.setScrapContainer(this, true); mChangedScrap.add(holder); } }
源码分析:
对于页面中显示的Item,当调用 LayoutManager 类的 onLayoutChildren() 方法对views进行布局,这时会将RecyclerView上的items全部暂存到一个 ArrayList 集合,这里的数据是没有做修改的,所以不用重新绑定 Adapter。而如果其他情况比如调用了 notifyItemChanged() 和 notifyItemRangeChanged() 来通知数据发生了更新,数据或位置发生改变,那么该ViewHolder会被缓存到mChangedScrap中,这里存储的是发生了变化的ViewHolder,所以要重新走Adapter的绑定方法。
图解:
图中的itemB删除掉,然后itemC,itemD依次移动上来,这里itemA和itemB前后参数没有发生变化(虽然itemB被移除了,但移除的时候它还是有效的,会被打上REMOVED标签,表示它是要删除的),所以他们两个存储到mAttachedscrap(),而itemC和itemD的位置发生了改变,所以他俩要存到mChangedScrap()中去。总结来说,删除itemB时,ABCD都会进入Scrap缓存,删除后,会从Scrap中将ACD取出,A的位置和数据都没有发生变化,CD的位置发生了变化但数据还是原封不动。
文章来源:https://blog.csdn.net/m0_51276753/article/details/125667231
复用 查找顺序:先判断是否为预布局,如果是则从
进入一级缓存的// Search in our first-level recycled view cache. 官方说这里是第一级,但在我们日常使用中还是称他为第二级缓存 // 查找过程
// 这是根据position来取 final int cacheSize = mCachedViews.size(); for (int i = 0; i < cacheSize; i++) { final ViewHolder holder = mCachedViews.get(i); // invalid view holders may be in cache if adapter has stable ids as they can be // retrieved via getScrapOrCachedViewForId // 这里要对索引进行判断,只有当位置对得上才能拿来复用, // 这也就意味着从mCatchedViews中取出的ViewHolder只能复用到指定的位置。 if (!holder.isInvalid() && holder.getLayoutPosition() == position && !holder.isAttachedToTransitionOverlay()) { // 如果不在容量范围内,就把ViewHolder丢出去,丢到缓存池中。 if (!dryRun) { mCachedViews.remove(i); } if (DEBUG) { Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position + ") found match in cache: " + holder); } return holder; } }
mAttachedScrapp和mCachedViews都是需要进行索引判断,也就是说从这两个缓存中取出的ViewHolder只能复用到指定的位置。mCachedViews只能缓存屏幕外它容量大小的ViewHolder,超出容量的部分会被移除,丢到缓存池中。
如果我们自定义了一个缓存并且前面的一二级缓存没有找到ViewHolder,系统就会从我们自定义的这个缓存里去找ViewHolder。
④四级
缓存
ViewHolder首先会缓存在 mCachedViews 中,当超过了个数(比如默认为2), 就会按照先入先出原则被移出mCachedViews列表,添加到 RecycledViewPool 中。
RecycledViewPool 会根据每个ViewType把ViewHolder分别存储在不同的列表中,
每个ViewType最多缓存DEFAULT_MAX_SCRAP = 5 个ViewHolder。
RecycledViewPool源码:
public static class RecycledViewPool { //同类ViewHolder缓存个数上限为5 private static final int DEFAULT_MAX_SCRAP = 5; // Tracks both pooled holders, as well as create/bind timing metadata for the given type. // 回收池中存放单个类型ViewHolder的容器 static class ScrapData { //同类ViewHolder存储在ArrayList中 ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; } //回收池中存放所有类型ViewHolder的容器 SparseArray<ScrapData> mScrap = new SparseArray<>(); ... //ViewHolder入池按viewType分类入池,一个类型的ViewType存放在一个ScrapData中 public void putRecycledView(ViewHolder scrap) { final int viewType = scrap.getItemViewType(); final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap; //如果超限了,则放弃入池 if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) { return; } if (DEBUG && scrapHeap.contains(scrap)) { throw new IllegalArgumentException("this scrap item already exists"); } scrap.resetInternal(); //回收时,ViewHolder从列表尾部插入 scrapHeap.add(scrap); } //从回收池中获取ViewHolder对象 public ViewHolder getRecycledView(int viewType) { // 获取到viewType final ScrapData scrapData = mScrap.get(viewType); if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap; //复用时,从列表尾部获取ViewHolder(优先复用刚入池的ViewHoler) return scrapHeap.remove(scrapHeap.size() - 1); } return null; } }
RecycledViewPool中的ViewHolder存储在SparseArray中,并且按viewType分类存储,同一类型的ViewHolder存放在一个ArrayList中。虽然没有了对索引的判断,但是从mRecyclerPool中取出的ViewHolder只能复用于相同viewType的表项。
holder = getRecycledViewPool().getRecycledView(type); if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
} // 这里是getRecyclerViewPool(),主要作用就是new了一个RecyclerViewPool对象出来。 // 然后再根据type来从缓存池中获取对应类型的ViewHolder。 RecycledViewPool getRecycledViewPool() { if (mRecyclerPool == null) { mRecyclerPool = new RecycledViewPool(); } return mRecyclerPool; }
当ViewHolder从缓存池取出来后,判断holder是否为空,如果不为空,说明holder从缓存池中取出来了。那么就执行 holder.resetInternal(),该方法是将数据,状态重置。