最全面的RecyclerView源码解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jieqiang3/article/details/68922821

相信很多人用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<ViewHolder> mAttachedScrap = new ArrayList<>();
private ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new  ArrayList<ViewHolder>();
private final List<ViewHolder>
                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<ArrayList<ViewHolder>> mScrap =
                new SparseArray<ArrayList<ViewHolder>>();
        private SparseIntArray mMaxScrap = new SparseIntArray();

//此处省略部分代码 
//……
public void setMaxRecycledViews(int viewType, int max) {
            mMaxScrap.put(viewType, max);
            final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
            if (scrapHeap != null) {
                while (scrapHeap.size() > max) {
                    scrapHeap.remove(scrapHeap.size() - 1);
                }
            }
        }

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;
        }

可以看到,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这些。第一次分析源码,如果有错误的地方请及时指出

阅读更多

没有更多推荐了,返回首页