RecyclerView是谷歌官方出的一个用于大量数据展示的新控件,可以用来代替传统的ListView,更加强大和灵活。
支持RecyclerView高效运行的主要六大类:
适配器的作用都是类似的,用于提供每个 item 视图,并返回给RecyclerView 作为其子布局添加到内部。
但是,与ListView不同的是,ListView的适配器是直接返回一个View,将这个 View加入到ListView内部。而RecyclerView是返回一个ViewHolder并且不是直接将这个holder加入到视图内部,而是加入到一个缓存区域,在视图需要的时候去缓存区域找到holder再间接的找到holder包裹的View。
我们写ListView的时候一般也会自己实现一个ViewHolder,它会持有一个View,避免不必要的findViewById(),来提高效率。
我们使用RecyclerView的时候必须创建一个继承自RecyclerView.ViewHolder的类。这主要是因为RecyclerView内部的缓存结构并不是像ListView那样去缓存一个View,而是直接缓存一个ViewHolder,在ViewHolder的内部又持有了一个View。既然是缓存一个ViewHolder,那么当然就必须所有的ViewHolder 都继承同一个类才能做到了。
顾名思义,LayoutManager是布局的管理者。实际上,RecyclerView就是将 onMeasure()、onLayout()交给了LayoutManager去处理,因此如果给 RecyclerView设置不同的LayoutManager就可以达到不同的显示效果。
RecyclerView的onMeasure()最终都会调用:mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
,这里的mLayout就是LayoutManager,最终还是调用了RecyclerView自己的方法对布局进行了测量。
RecyclerView的onLayout()中会调用:mLayout.onLayoutChildren(mRecycler, mState);
,这个方法在LayoutManager中是空实现:
public void onLayoutChildren(Recycler recycler, State state) {
Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
所以LayoutManager的子类都应该实现onLayoutChildren(),这里就将layout()的工作交给了LayoutManager的实现类,来完成对子View的布局。
ItemDecoration是为了显示每个item之间分隔样式的。它的本质实际上就是一个Drawable。当RecyclerView执行到onDraw()方法的时候,就会调用到他的 onDraw(),这时,如果你重写了这个方法,就相当于是直接在RecyclerView 上画了一个Drawable表现的东西。而最后,在他的内部还有一个叫getItemOffsets()的方法,从字面就可以理解,他是用来偏移每个item视图的。当我们在每个item视图之间强行插入绘画了一段 Drawable,那么如果再照着原本的逻辑去绘 item视图,就会覆盖掉Decoration了,所以需要getItemOffsets()这个方法,让每个item往后面偏移一点,不要覆盖到之前画上的分隔样式了。
每一个item在特定情况下都会执行的动画。说是特定情况,其实就是在视图发生改变,我们手动调用notifyxxxx()的时候。通常这个时候我们会要传一个下标,那么从这个标记开始一直到结束,所有 item 视图都会被执行一次这个动画。
public final class Recycler {
final ArrayList mAttachedScrap = new ArrayList<>();
ArrayList mChangedScrap = null;
final ArrayList mCachedViews = new ArrayList();
private final List
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
private RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
……
}
RecyclerView拥有四级缓存:
屏幕内缓存 :指在屏幕中显示的ViewHolder,这些ViewHolder会缓存在mAttachedScrap、mChangedScrap中 。
屏幕外缓存:当列表滑动出了屏幕时,ViewHolder会被缓存在 mCachedViews,其大小由mViewCacheMax决定,默认DEFAULT_CACHE_SIZE为2,可通过Recyclerview.setItemViewCacheSize()动态设置。
自定义缓存:可以自己实现ViewCacheExtension类实现自定义缓存,可通过Recyclerview.setViewCacheExtension()设置。通常我们也不会去设置他,系统已经预先提供了两级缓存了,除非有特殊需求,比如要在调用系统的缓存池之前,返回一个特定的视图,才会用到他。
缓存池 :ViewHolder首先会缓存在mCachedViews中,当超过了2个(默认为2),就会添加到mRecyclerPool中。mRecyclerPool会根据ViewType把ViewHolder分别存储在不同的集合中,每个集合最多缓存5个ViewHolder。
缓存策略:
在LayoutManager执行layoutChildren()中获取子View的时候,会调用RecyclerView的getViewForPosition():
View getViewForPosition(int position, boolean dryRun) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("Invalid item position " + position
+ "(" + position + "). Item count:" + mState.getItemCount());
}
boolean fromScrap = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
// 从mChangedScrap找
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
// 1) Find from scrap by position
if (holder == null) {
// 通过position从mAttachedScrap找,找不到再通过position从mCachedViews找
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle this scrap
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrap = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount());
}
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
// 通过id从mAttachedScrap找,找不到再通过id从mCachedViews找
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// 从mViewCacheExtension找
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder");
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view.");
}
}
}
if (holder == null) { // fallback to recycler
// 从mRecyclerPool找
// try recycler.
// Head to the shared pool.
if (DEBUG) {
Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
+ "pool");
}
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
// 从缓存中找不到,创建新的ViewHolder
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
}
……
}
Recyclerview在获取ViewHolder时按四级缓存的顺序查找,如果没找到就创建。
通过了解RecyclerView的四级缓存,我们可以知道,RecyclerView最多可以缓存N(屏幕最多可显示的item数)+ 2 (屏幕外的缓存) + 5*M (M代表M个ViewType,缓存池的缓存)。
还需要注意的是,RecyclerViewPool可以被多个RecyclerView共享。
参考:
1.Android ListView 与 RecyclerView 对比浅析:缓存机制
2.深入浅出 RecyclerView
3.关于Recyclerview的缓存机制的理解