相信很多人用RecyclerView已经很久了,但还是不得不感叹 RecyclerView的强大,性能、扩展性等方面都很强大。网上看了很多源码方面对RecyclerView,觉得还不够全面,而且自己不走一遍源码总感觉会很容易忘记。
打开RecyclerView类,发现有11090行代码,千万不要震惊,慢慢看。
整体看了下RecyclerView的架构,你会惊奇于这个优雅的设计,高度解耦,灵活性很强,给开发者一种插拔式的体验,使用者只要通过设置不同的LayoutManager, ItemDecoration, ItemAnimator就可以实现各种各样的效果了。
##Recycler
打开RecyclerView的结构,复杂,我们就从上往下说好了。首先来分析Recycler这个内部类,Recycler是整个RecyclerView的精髓所在,那么Recycler到底是什么呢:
Recycler的职责是管理那些已经废弃了的或者从RecyclerView中分离的item view用于复用。
废弃的View是指那些仍然依附于RecyclerView但是已经被标记为可以被移除或者复用的View。
Recycler典型的用法就是当LayoutManager去获取Adapter中的某一项View的时候,如果这个View失效了,则需要重新绑定View,当复用的View是有效的话,View就会被直接被复用。有效的View如果不主动调用requestLayout,那么该View不需要重新测量就可以被复用。
说了一大堆,还是边看代码边解释更加清晰一点,首先来看Recycler几个成员变量:
final ArrayList mAttachedScrap = new ArrayList<>();
private ArrayList mChangedScrap = null;
final ArrayList mCachedViews = new ArrayList();
private final List
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mViewCacheMax = DEFAULT_CACHE_SIZE;
private RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
先来看RecycledViewPool和ViewCacheExtension这两个类:
RecycledViewPool让开发者可以在多个RecyclerView之间共享View。
如果你想要垮RecyclerView复用View,创建一个RecycledViewPool实例,然后调用setRecycledViewPool(RecycledViewPool)方法就可以了。
RecyclerView会自动创建一个RecycledViewPool的实例。
有了RecycledViewPool的存在,就能很大程度上减少View的创建,提高性能。
先看下面这两个方法:
private SparseArray> mScrap =
new SparseArray>();
private SparseIntArray mMaxScrap = new SparseIntArray();
//此处省略部分代码
//……
public void setMaxRecycledViews(int viewType, int max) {
mMaxScrap.put(viewType, max);
final ArrayList scrapHeap = mScrap.get(viewType);
if (scrapHeap != null) {
while (scrapHeap.size() > max) {
scrapHeap.remove(scrapHeap.size() - 1);
}
}
}
public ViewHolder getRecycledView(int viewType) {
final ArrayList 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;
}
可以看到,mScrap是一个《viewType, List>的映射,mMaxScrap是一个《viewType, maxNum>的映射,我们可以调用setMaxRecycledViews方法来设置每种viewType的view容量。从源码可以看出,如果viewType类型的list的size大于制定的最大数字的话,会先从列表的末尾开始丢弃超出的部分。调用getRecycledView(int viewType)方法呢,可以将scrapHeap中的最后一项移除并返回viewType对应的List的末尾项。这里需要注意的是,因为是跨RecyclerView进行操作,所以要特别注意对于同一个RecycledViewPool,对ViewType的定义要统一,因为这里是根据viewType来取ViewHolder的。
再来看ViewCacheExtension这个类:
public abstract static class ViewCacheExtension {
abstract public View getViewForPositionAndType(Recycler recycler, int position, int type);
}
ViewCacheExtension是一个帮助类,给开发者提供了一个可以由他们自己控制View缓存的这么一个类。当调用Recycler的getViewForPosition(int position)方法等时候,Recycler会先去检查attached scrap和一级缓存来这个View,如果都找不到匹配的View的话,Recycler会调用ViewCacheExtension的getViewForPositionAndType方法检查,如果还是没有,再去检查RecycledViewPool。
需要注意的是:Recycler不会在这个类中做任何缓存View的操作,是否需要缓存View由开发者自己控制。
然后回到Recycler,Recycler最终目的还是要去获取到来看这个方法:
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()) {
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
// 1) Find from scrap by position
if (holder == null) {
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()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// 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
// 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) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
}
// This is very ugly but the only place we can grab this information
// before the View is rebound and returned to the LayoutManager for post layout ops.
// We don't need this in pre-layout since the VH is not updated by the LM.
if (fromScrap && !mState.isPreLayout() && holder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (mState.mRunSimpleAnimations) {
int changeFlags = ItemAnimator
.buildAdapterChangeFlagsForAnimations(holder);
changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
holder, changeFlags, holder.getUnmodifiedPayloads());
recordAnimationInfoIfBouncedHiddenView(holder, info);
}
}
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()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder);
}
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;
}
}
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;
}
从代码中可以看出,要获取到某个position下的view,有这么一个过程:
首先Recycler会先去检查mChangedScrap,如果匹配成功则返回相应的viewHolder。mChangedScrap就是所谓的detachedView,与RecyclerView分离的viewHolder列表。
然后是mAttachedScrap,mViewCacheExtension,mRecyclerPool,如果都没有,则去创建,调用Adapter.createViewHolder()。
其实相当于RecyclerView也做到了二级缓存的概念,mCachedViews是一层,默认大小是DEFAULT_CACHE_SIZE = 2, 还有一层就是RecycledViewPool。我们可以调用这个方法来控制第一层的缓存数量:
public void setViewCacheSize(int viewCount) {
mViewCacheMax = viewCount;
// first, try the views that can be recycled
for (int i = mCachedViews.size() - 1; i >= 0 && mCachedViews.size() > viewCount; i--) {
recycleCachedViewAt(i);
}
}
这就是Recycler去根据position获取相应的itemView的过程,既然有获取,那么就肯定有放入,来看这个方法:
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) {
recycleCachedViewAt(0);
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
addViewHolderToRecycledViewPool(holder);
recycled = true;
}
} else if (DEBUG) {
Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ "re-visit here. We are still removing it from animation lists");
}
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
这个方法其实是先去判断了下mCachedViews是否已经满了,如果满了,则移除一个到recycledViewPool中去,然后将新的itemView添加到mCachedViews,如果没满那最好了,直接添加。
##LayoutManager
LayoutManager的主要职责就是去测量和摆放itemView和当itemView对用户来说不再能看见的时候去复用它。LayoutManager牵扯到的东西其实也蛮多的,我特意去翻看了下LinearLayoutManager的源码,挺复杂的,就不再这篇讲了,到时候另起一篇来讲下这个东西。
可以看到,RecyclerView的onMeasure和onLayout方法都是交给了LayoutManager来处理。
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
//省略一段代码
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
mLayout.setMeasureSpecs(widthSpec, heightSpec);
//省略一段代码
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
//省略一段代码。。。。。。
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
eatRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
resumeRequestLayout(false);
mState.mInPreLayout = false;
}
}
##滑动
RecyclerView的onTouchEvent方法比较长,这里就不贴了。其实滑动的套路都差不多的,RecyclerView也一样。那就讲一下我看了源码后的理解吧,这里以垂直方向的滑动为力。
在ACTION_MOVE中,首先计算出手指滑动的距离,先跟TouchSlop进行比较,如果大雨这个值,那么就是确定为师滑动了,而不是点击,然后调用scrollByInternal()方法,scrollByInternal其实最后还是用了scrollBy方法,拖拽性质的滑动就解决了。当然我们也发现当我们拖拽RecyclerView后,当我们的手指离开屏幕以后,RecyclerView还会滑动一段时间,这个其实就是fling事件。RecyclerView在ACTION_UP事件的时候,VelocityTracker根据拖拽时候滑动的距离和时间计算出一个速度,然后调用fling方法就行了,其实fling里面还是用Scrooler来实现的。这里贴下fling方法的代码:
public void fling(int velocityX, int velocityY) {
setScrollState(SCROLL_STATE_SETTLING);
mLastFlingX = mLastFlingY = 0;
mScroller.fling(0, 0, velocityX, velocityY,
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
postOnAnimation();
}
源码这个东西真的是越看越多,尤其RecyclerView感觉更是。还是分篇写吧打算,下一篇继续分析下LayoutManager,ViewHolder,ItemDecoration这些。第一次分析源码,如果有错误的地方请及时指出