Android RecyclerView 原理解析
android最原始的复用型列表View是ListView,具体原理可以参照 这篇文章。
一.模块分析
1.列表控件
继承关系
2.LayoutManager
RecyclerView支持列表、表格、瀑布流三种布局样式,与ListView自身决定布局样式不同的是,RecyclerView使用其静态内部类LayoutManager来管理具体的布局样式,其子类可以实现自己的对于子view的测量,布局等功能
RecyclerView提供了三种LayoutManager,LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager,用户还可以通过继承LayoutManager来实现自己的布局管理器
3.Recycler回收机制
这货就是回收view的主要类,他是RecyclerView的一个内部类,管理着对view的分类分级回收,与ListView的RecycleBin类功能类似
class Recycler{
//主要field
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();//回收attached的view的,与ListView的activeViews类似
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();//一级缓存,可设置大小,默认缓存大小为2
private RecycledViewPool mRecyclerPool;//缓存池对象,多个RecyclerView可以指定使用一个pool对象
private ViewCacheExtension mViewCacheExtension;//自定义的缓存机制对象,用户可以继承之实现自己的一个缓存对象,在指定时机会使用该对象里的缓存对象
//主要方法
//1.获取缓存view
View getViewForPosition(int position, boolean dryRun) {
...
// 1) Find from scrap by position
if (holder == null) {
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);//从scrapView里(attachViews或者一级缓存cachedViews)根据position获取view
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle this scrap
...
} else {
fromScrap = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
...
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
//有stableId就从scrapView里(attachViews或者一级缓存cachedViews)根据itemId获取view
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
if (holder == null && mViewCacheExtension != null) {
//此时如果有自定义的cache会尝试从中复用
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
...
}
if (holder == null) {
// fallback to recycler
...
holder = getRecycledViewPool().getRecycledView(type);//从pool里获取
...
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);//新建ViewHolder
...
}
}
...
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
...
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
holder.mOwnerRecyclerView = RecyclerView.this;
mAdapter.bindViewHolder(holder, offsetPosition);//需要绑定数据
attachAccessibilityDelegate(holder.itemView);
bound = true;
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
}
//设置lp
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrap && bound;
return holder.itemView;
}
//2.从pool里获取指定type的缓存对象
public ViewHolder getRecycledView(int viewType) {
final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
if (scrapHeap != null && !scrapHeap.isEmpty()) {
final int index = scrapHeap.size() - 1;
final ViewHolder scrap = scrapHeap.get(index);
scrapHeap.remove(index);
return scrap;
}
return null;
}
//3.回收ViewHolder
void recycleViewHolderInternal(ViewHolder holder) {
...
if (forceRecycle || holder.isRecyclable()) {
if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE)) {
// Retire oldest cached view
final int cachedViewSize = mCachedViews.size();
if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
//一级缓存满了的话移除一个到pool里,然后再放入到一级缓存里
recycleCachedViewAt(0);
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
//如果没有加入到一级缓存中,则放入pool里缓存
addViewHolderToRecycledViewPool(holder);
recycled = true;
}
}...
}
//4.根据type放入到指定type的缓存集合中
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList scrapHeap = getScrapHeapForType(viewType);
...
scrap.resetInternal();
scrapHeap.add(scrap);
}
//5.缓存attach的view
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
//满足条件的attach的view放入attached集合中
...
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
}...
}
}
可以通过RecyclerView的setRecycledViewPool来设置该view的pool对象,exa,一个ViewPager的多个RecyclerView展示使用相同种类的view和type,就可以公用一个pool来缓存对象,提升缓存效率
可以通过RecyclerView的setItemViewCacheSize来设置一级缓存对象个数,默认为2
可以通过RecyclerView的setViewCacheExtension来设置自定义的扩展缓存,用户实现时重写getViewForPositionAndType方法,根据position和type来决定复用哪个对象(该cache并不会去缓存对象),在从pool里取之前调用该方法进行复用
二.View布局流程分析
这里说明一下23.2.0后RecyclerView支持height为WRAP_CONTENT情况(之前版本都是和MATCH_PARENT一样处理),该情况会在onMeausre时就调用dispatchLayoutStep2进行布局,尽量不要使用
布局子view主要是通过不同的LayoutManager实现的onLayoutChildren方法实现,大体实现思路就是计算当前绘制点的信息,包括坐标点和item的position等,并循环在剩余空间(最多一屏)里获取view并填充,该view的获取由多级缓存实现,包括attach的集合、一级缓存的集合、自定义的缓存以及可公用缓存池
与ListView的detach+attach填充view不同的是,RecyclerView在回收view时remove掉,再次复用时add进来,理论上会触发多次requestLayout,但是RecyclerView重写了该方法,在里面有拦截逻辑,使得不会触发多次,所以不用担心效率问题
dispatchLayoutStep1和3主要处理notifyItem及其动画事件,后续会单独分析,这里只说实际布局子view的dispatchLayoutStep2
1.LinearLayoutManager的onLayoutChildren()方法
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
...
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);//更新布局锚点信息
...
detachAndScrapAttachedViews(recycler);//detach和recycle当前所有view