最全面的RecyclerView源码解析(一)

来源:互联网 发布:安卓开发艺术探索源码 编辑:程序博客网 时间:2024/05/17 03:30

相信很多人用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这些。第一次分析源码,如果有错误的地方请及时指出

0 0
原创粉丝点击