I believe that many people have used Recycler View for a long time, but still have to marvel at the power of Recycler View, performance, scalability and other aspects are very strong. Looking at a lot of source code on the Internet, RecyclerView feels not comprehensive enough, and it's easy to forget that you don't go through the source code once. Open the RecyclerView class and find 11090 lines of code. Don't be shocked. Take your time. Look at Recycler View as a whole Framework You'll be amazed at this elegant design, highly decoupled and flexible, which gives developers a plug-in experience. Users can achieve all kinds of effects by setting up different Layout Managers, ItemDecorations and ItemAnimators.
Recycler
Open the structure of Recycler View, complex, let's talk from top to bottom. First of all, we will analyze Recycler as an internal class. Recycler is the essence of the whole Recycler View. So what is Recycler exactly?
Recycler's responsibility is to manage items that have been abandoned or separated from Recycler View for reuse.
Abandoned Views are those that are still attached to RecyclerView but have been marked as views that can be removed or reused.
A typical use of Recycler is that when LayoutManager goes to get a View in the Adapter, if the View fails, it needs to rebind the View. When the reused View is valid, the View will be reused directly. A valid View can be reused without re-measuring if it does not actively call requestLayout.
Having said a lot, or looking at the code and explaining it more clearly, let's first look at Recycler's member variables:
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;
Let's start with Recycled ViewPool and ViewCacheExtension classes:
Recycled ViewPool allows developers to share views among multiple Recycler Views.
If you want to break RecyclerView multiplexing View, create a RecycledViewPool instance, and then call the setRecycledViewPool(RecycledViewPool) method.
RecyclerView automatically creates an instance of RecycledViewPool.
With the existence of Recycled ViewPool, you can greatly reduce the creation of View and improve performance.
First look at the following two methods:
private SparseArray<ArrayList<ViewHolder>> mScrap = new SparseArray<ArrayList<ViewHolder>>(); private SparseIntArray mMaxScrap = new SparseIntArray(); //Some code is omitted here //...... 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; }
As you can see, mScrap is a mapping of viewType, List, and mMaxScrap is a mapping of viewType, maxNum. We can call the setMaxRecycledViews method to set the viewcapacity of each viewType. As you can see from the source code, if the size of the list of viewType type is larger than the maximum number set, the excess part will be discarded from the end of the list. Calling the getRecycledView(int viewType) method removes the last item in scrapHeap and returns the end item of the List corresponding to viewType. It is important to note here that, because it operates across RecyclerView, special attention should be paid to unifying the definition of ViewType for the same RecycledViewPool, because ViewHolder is fetched here according to viewType.
Look again at the ViewCacheExtension class:
public abstract static class ViewCacheExtension { abstract public View getViewForPositionAndType(Recycler recycler, int position, int type); }
ViewCacheExtension is a helper class that gives developers a class that they can control the View cache themselves. When invoking Recycler's getViewForPosition(int position) method and so on, Recycler checks the attached scrap and first-level cache for the View. If no matching View is found, Recycler calls ViewCacheExtension's getViewForPositionAndType method to check, and if not, to check RecycledVi. EwPool.
It should be noted that Recycler does not do any caching of Views in this class, and whether caching Views is required is under the developer's own control.
Then back to Recycler, Recycler's ultimate goal is to get to see this method:
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; }
As you can see from the code, to get a view under a position, there is such a process:
First Recycler checks mChangedScrap and returns the corresponding viewHolder if the match is successful. MChangedScrap is the so-called detached View, a list of viewHolders separated from RecyclerView.
Then mAttachedScrap, mViewCacheExtension, mRecyclerPool, if not, to create, call Adapter.createViewHolder().
In fact, it is equivalent to RecyclerView, which also achieves the concept of secondary caching. mCachedViews is a layer, the default size is DEFAULT_CACHE_SIZE = 2, and the other layer is RecycledViewPool. We can call this method to control the number of caches in the first layer:
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); } }
This is Recycler's process of getting the corresponding itemView based on position. Since it has been acquired, it must have been put in. Look at this method:
void recycleViewHolderInternal(ViewHolder holder) { //Eliminate part of the code... 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; } }
This method is to determine whether the next mCachedViews is full or not. If it is full, remove one to the recycled ViewPool, and then add the new itemView to the mCachedViews. If not, it's best to add it directly.
LayoutManager
The main responsibility of LayoutManager is to measure and place itemView and reuse it when itemView is no longer visible to users. Layout Manager involves a lot of things. I went to look at the source code of Linear Layout Manager. It's very complicated. I won't talk about this one anymore. I'll start another one to talk about this one.
As you can see, the onMeasure and onLayout methods of RecyclerView are handed over to LayoutManager for processing.
@Override protected void onMeasure(int widthSpec, int heightSpec) { //Omit a piece of code 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); //Omit a piece of code } else { if (mHasFixedSize) { mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); return; } //Eliminate a piece of code... if (mAdapter != null) { mState.mItemCount = mAdapter.getItemCount(); } else { mState.mItemCount = 0; } eatRequestLayout(); mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); resumeRequestLayout(false); mState.mInPreLayout = false; } }
Slide
RecyclerView's onTouchEvent method is longer, so it's not posted here. In fact, the sliding routines are similar, and so is Recycler View. Let's talk about what I understand when I look at the source code. Here's the slip in the vertical direction.
In ACTION_MOVE, the distance of finger sliding is calculated first, and compared with TouchSlop. If the value of heavy rain, it is determined that the teacher slides instead of clicking. Then scrollByInternal() method is called. In fact, scrollByInternal still uses scrollBy method, and dragging sliding is solved. Of course, we also found that when we drag RecyclerView, when our fingers leave the screen, RecyclerView will slide for a while, which is actually fling event. When RecyclerView is in ACTION_UP event, Velocity Tracker calculates a speed based on the distance and time of sliding when dragging, and then calls the fling method. In fact, fling is still implemented by Scrooler. Here is the code for the fling method:
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(); }
Source code is really the more you look at it, especially Recycler View. Let's write it in sections. The next article will continue to analyze Layout Manager, ViewHolder, ItemDecoration and so on. The first analysis of source code, if there are errors, please point out in time
