前言

RecyclerView已经出来很久,现在几乎应该都会用RecyclerView代替Listview,虽然我觉得大多数人应该还是不太清楚这两者之前的区别的,或者说RecyclerView相对于Listview到底好在哪里。我平时也只是很简单的使用一下,并没有对其原理进行深度挖掘,现在刚好公司项目不忙,就花点时间研究一下它的源码。

内容

类继承关系

我觉得研究任何一个类的源码首先应该知道其类的继承关系,这样我们可以对它有一个整体的认识,比如TextView继承自View,那它就会有View的一些特性。所以先来看下RecyclerView的继承关系:

可以看到它直接继承于ViewGroup,所以它是个容器(废话,哈哈),还有它的子类有BaseGridView,WearableRecyclerView,HorizontalGridView,VerticalGridView,我们暂时这里不研究子类。

类注释

除了类继承关系,类注释也是很重要的部分,因为它往往介绍了这个类的特性,以及一些关键概念,我们这里就来看下RecyclerView的注释,原文如下

A flexible view for providing a limited window into a large data set.
Glossary of terms:
Adapter: A subclass of RecyclerView.Adapter responsible for providing views that represent items in a data set.
Position: The position of a data item within an Adapter.
Index: The index of an attached child view as used in a call to getChildAt(int). Contrast with Position.
Binding: The process of preparing a child view to display data corresponding to a position within the adapter.
Recycle (view): A view previously used to display data for a specific adapter position may be placed in a cache for later reuse to display the same type of data again later. This can drastically improve performance by skipping initial layout inflation or construction.
Scrap (view): A child view that has entered into a temporarily detached state during layout. Scrap views may be reused without becoming fully detached from the parent RecyclerView, either unmodified if no rebinding is required or modified by the adapter if the view was considered dirty.
Dirty (view): A child view that must be rebound by the adapter before being displayed.
Positions in RecyclerView:
RecyclerView introduces an additional level of abstraction between the RecyclerView.Adapter and RecyclerView.LayoutManager to be able to detect data set changes in batches during a layout calculation. This saves LayoutManager from tracking adapter changes to calculate animations. It also helps with performance because all view bindings happen at the same time and unnecessary bindings are avoided.

For this reason, there are two types of position related methods in RecyclerView:

layout position: Position of an item in the latest layout calculation. This is the position from the LayoutManager’s perspective.
adapter position: Position of an item in the adapter. This is the position from the Adapter’s perspective.
These two positions are the same except the time between dispatching adapter.notify* events and calculating the updated layout.

Methods that return or receive LayoutPosition use position as of the latest layout calculation (e.g. getLayoutPosition(), findViewHolderForLayoutPosition(int)). These positions include all changes until the last layout calculation. You can rely on these positions to be consistent with what user is currently seeing on the screen. For example, if you have a list of items on the screen and user asks for the 5th element, you should use these methods as they’ll match what user is seeing.

The other set of position related methods are in the form of AdapterPosition. (e.g. getAdapterPosition(), findViewHolderForAdapterPosition(int)) You should use these methods when you need to work with up-to-date adapter positions even if they may not have been reflected to layout yet. For example, if you want to access the item in the adapter on a ViewHolder click, you should use getAdapterPosition(). Beware that these methods may not be able to calculate adapter positions if notifyDataSetChanged() has been called and new layout has not yet been calculated. For this reasons, you should carefully handle NO_POSITION or null results from these methods.

When writing a RecyclerView.LayoutManager you almost always want to use layout positions whereas when writing an RecyclerView.Adapter, you probably want to use adapter positions.

呵呵,辣么大一串,就算是中文都懒的看,何况是英文,可四既然是写博客,就是要做出贡献,只能咬紧牙关看下去。经过吭哧吭哧的阅读翻译,大概意思如下:

RecyclerView是一个灵活的view,用来在有限的窗口中显示大量的数据集。呵呵,官方文档这么写,我也很绝望。
在真正开始阅读源码前,先介绍几个关键名词:

Adapter:RecyclerView.Adapter的子类,用来提供显示数据条目的视图(从介绍来看和ListView的adapter差不多)

Position:adapter中数据条目的位置

Index:已经添加的子view的索引,也就是item视图,在getChildAt(int)会被用到。和position要区别开来,position是数据的位置,index是视图的位置

Binding: 将adapter中的数据显示到每一个child view中的过程。

Recycle (view):就是一个可以复用的view,这可以大幅提高性能,因为省去了初始化和构造的过程

Scrap (view):什么是Scrap view呢?就是指的那些还没有被detached,但是已经被标记为removal或者reuse,Scrap views被复用的时候有两种情况,一种是完全不改变内容,因为数据没有发生改变,另外一种是当这个view被认是dirty,的时候,这样就要重新绑定数据(从后面代码来看,其实scrap的view指的是那些已经调用了detach方法,但并没有被remove的view,只是将parent设为null,在视图中仍然存在)

Dirty (view): dirty view指的是那些在展示之前必须重新绑定数据的view

上面提到的view都是指RecyclerView中的item view

关键名词介绍到这里,大家应该都看的懂。

接下来重点介绍了RecyclerView中positions概念

大概意思是Recyclerview在RecyclerView.Adapter和RecyclerView.LayoutManager之间采用了一种额外的抽象,使得可以在布局计算的时候侦测到大批量数据的变化,这可以将LayoutManager从跟踪adapter的数据变化中解脱出来,而去从事计算动画的工作。( to calculate animations这段意思不确定,特别是to在这里的意思,先放着,等看了源码再说)。这样还可以提高性能表现,因为所有的view binding在同一时间完成,避免了不要的binding(一脸闷逼,还是先放着)。

因为这个原因,所以在RecyclerView中有两类position相关的方法。

layout position: 在最近一次布局计算后item的位置,这个位置是站在LayoutManager的角度来说
adapter position: item在adpater中的位置,这是站在Adapter的角度来说

个人认为layout position就是指视图上item的位置,而adapter position就是指在data set中的位置
这两个position在大多数时候是相等的,只有当adapter.notify*已经被调用,而布局还没有刷新之前这段时间是不一样的。

那些返回或者接受*LayoutPosition*的方法使用的是最近一次布局计算的位置,比如getLayoutPosition()findViewHolderForLayoutPosition(int)。这些position就是用户在屏幕上看到的样子。举个例子,如果你有一个list在屏幕上展示,然后用户想要第五个条目,你就应该使用这些方法。

另外一系列position相关的方法格式是这样的*AdapterPosition*,如getAdapterPosition(), findViewHolderForAdapterPosition(int)。如果你需要和最新的adapter position打交道,不管它是否已经反映到布局中,那你就应该使用这系列方法。比如,如果你希望在ViewHolder click中访问adapter中的item,你就应该使用getAdapterPosition(),有一点要注意,就是当notifyDataSetChanged已经被调用,而布局还没有计算完成,这时候就不能使用这些方法去计算adapter的position。所以,当你在使用这些方法的时候需要特别注意处理返回值为NO_POSITION 的情况。

总的来说就是当你在写RecyclerView.LayoutManager的时候,基本上就应该使用layout positions,而在写Adapter的时候就应该使用adapter positions

你看,仔细看一下注释还是很有用的吧。

构造函数

好了接下来就是真正开始啃源码的时候了。首先当然从构造函数开始,既然是继承自View,那肯定也就是少不了View类似的那些构造函数。

先从RecyclerView(Context context)看起。

 public RecyclerView(Context context) {this(context, null);}

直接调用RecyclerView(Context context, AttributeSet attrs),

public RecyclerView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}

所以最后就看public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle)
源码不是很多,就全部贴上来了

 public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);if (attrs != null) {TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);mClipToPadding = a.getBoolean(0, true);a.recycle();} else {mClipToPadding = true;}setScrollContainer(true);setFocusableInTouchMode(true);final ViewConfiguration vc = ViewConfiguration.get(context);mTouchSlop = vc.getScaledTouchSlop();mScaledHorizontalScrollFactor =ViewConfigurationCompat.getScaledHorizontalScrollFactor(vc, context);mScaledVerticalScrollFactor =ViewConfigurationCompat.getScaledVerticalScrollFactor(vc, context);mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);mItemAnimator.setListener(mItemAnimatorListener);initAdapterManager();initChildrenHelper();// If not explicitly specified this view is important for accessibility.if (ViewCompat.getImportantForAccessibility(this)== ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {ViewCompat.setImportantForAccessibility(this,ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);}mAccessibilityManager = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));// Create the layoutManager if specified.boolean nestedScrollingEnabled = true;if (attrs != null) {int defStyleRes = 0;TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,defStyle, defStyleRes);String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);int descendantFocusability = a.getInt(R.styleable.RecyclerView_android_descendantFocusability, -1);if (descendantFocusability == -1) {setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);}mEnableFastScroller = a.getBoolean(R.styleable.RecyclerView_fastScrollEnabled, false);if (mEnableFastScroller) {StateListDrawable verticalThumbDrawable = (StateListDrawable) a.getDrawable(R.styleable.RecyclerView_fastScrollVerticalThumbDrawable);Drawable verticalTrackDrawable = a.getDrawable(R.styleable.RecyclerView_fastScrollVerticalTrackDrawable);StateListDrawable horizontalThumbDrawable = (StateListDrawable) a.getDrawable(R.styleable.RecyclerView_fastScrollHorizontalThumbDrawable);Drawable horizontalTrackDrawable = a.getDrawable(R.styleable.RecyclerView_fastScrollHorizontalTrackDrawable);initFastScroller(verticalThumbDrawable, verticalTrackDrawable,horizontalThumbDrawable, horizontalTrackDrawable);}a.recycle();createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);if (Build.VERSION.SDK_INT >= 21) {a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS,defStyle, defStyleRes);nestedScrollingEnabled = a.getBoolean(0, true);a.recycle();}} else {setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);}// Re-set whether nested scrolling is enabled so that it is set on all API levelssetNestedScrollingEnabled(nestedScrollingEnabled);}

首先是调用父类构造方法,不鸟他,继续往下,

 if (attrs != null) {TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);mClipToPadding = a.getBoolean(0, true);a.recycle();} else {mClipToPadding = true;}

就是初始化了mClipToPadding变量。

   setScrollContainer(true);

这是为scroll容器,设置为true后如果打开软件盘,view会被压缩。

   setFocusableInTouchMode(true);final ViewConfiguration vc = ViewConfiguration.get(context);mTouchSlop = vc.getScaledTouchSlop();mScaledHorizontalScrollFactor =ViewConfigurationCompat.getScaledHorizontalScrollFactor(vc, context);mScaledVerticalScrollFactor =ViewConfigurationCompat.getScaledVerticalScrollFactor(vc, context);mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);

基础设置,不管它

mItemAnimator.setListener(mItemAnimatorListener);

设置itemAnimatorListener,当item动画结束的时候,这个listeneronAnimationFinished方法必须被调用

initAdapterManager();

看方法代码,就是初始化了一个AdapterHelper实例,这个AdapterHelper类是干什么的呢?看类注释是说可以将adapter的更新动作加入队列,并进行处理,先不深究,等下回头再来看。

initChildrenHelper();

看代码是初始化了一个ChildHelper实例,这个类操作child view的中间层,具体可以看这篇文章 RecyclerView机制解析: ChildHelper。
接下来,我们忽略通用code,只看RecyclerView特有code

String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);

获取LayoutManagerName,所以LayoutManagerName是可以通过xml指定的。

createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);

上面从xml中获取到的layoutManagerName就在这里派上用场了,如果layoutManagerNamenull,就直接return了。如果不为null,则通过类名来实例化一个LayoutManager,并调用setLayoutManager方法。

 private void createLayoutManager(Context context, String className, AttributeSet attrs,int defStyleAttr, int defStyleRes) {if (className != null) {className = className.trim();if (!className.isEmpty()) {className = getFullClassName(context, className);try {ClassLoader classLoader;if (isInEditMode()) {// Stupid layoutlib cannot handle simple class loaders.classLoader = this.getClass().getClassLoader();} else {classLoader = context.getClassLoader();}Class<? extends LayoutManager> layoutManagerClass =classLoader.loadClass(className).asSubclass(LayoutManager.class);Constructor<? extends LayoutManager> constructor;Object[] constructorArgs = null;try {constructor = layoutManagerClass.getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE);constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes};} catch (NoSuchMethodException e) {try {constructor = layoutManagerClass.getConstructor();} catch (NoSuchMethodException e1) {e1.initCause(e);throw new IllegalStateException(attrs.getPositionDescription()+ ": Error creating LayoutManager " + className, e1);}}constructor.setAccessible(true);setLayoutManager(constructor.newInstance(constructorArgs));} catch (ClassNotFoundException e) {throw new IllegalStateException(attrs.getPositionDescription()+ ": Unable to find LayoutManager " + className, e);} catch (InvocationTargetException e) {throw new IllegalStateException(attrs.getPositionDescription()+ ": Could not instantiate the LayoutManager: " + className, e);} catch (InstantiationException e) {throw new IllegalStateException(attrs.getPositionDescription()+ ": Could not instantiate the LayoutManager: " + className, e);} catch (IllegalAccessException e) {throw new IllegalStateException(attrs.getPositionDescription()+ ": Cannot access non-public constructor " + className, e);} catch (ClassCastException e) {throw new IllegalStateException(attrs.getPositionDescription()+ ": Class is not a LayoutManager " + className, e);}}}}

到这里构造函数就完了。

setLayoutManager方法

按照一般的使用方法,接下来我们需要通过setLayoutManager方法手动指定一个LayoutManager,方法源码如下:

    public void setLayoutManager(LayoutManager layout) {if (layout == mLayout) {return;}stopScroll();// TODO We should do this switch a dispatchLayout pass and animate children. There is a good// chance that LayoutManagers will re-use views.if (mLayout != null) {// end all running animationsif (mItemAnimator != null) {mItemAnimator.endAnimations();}mLayout.removeAndRecycleAllViews(mRecycler);mLayout.removeAndRecycleScrapInt(mRecycler);mRecycler.clear();if (mIsAttached) {mLayout.dispatchDetachedFromWindow(this, mRecycler);}mLayout.setRecyclerView(null);mLayout = null;} else {mRecycler.clear();}// this is just a defensive measure for faulty item animators.mChildHelper.removeAllViewsUnfiltered();mLayout = layout;if (layout != null) {if (layout.mRecyclerView != null) {throw new IllegalArgumentException("LayoutManager " + layout +" is already attached to a RecyclerView: " + layout.mRecyclerView);}mLayout.setRecyclerView(this);if (mIsAttached) {mLayout.dispatchAttachedToWindow(this);}}mRecycler.updateViewCacheSize();requestLayout();}

首先会判断新的LayoutManager是不是和旧的一样,一样就直接返回,否则就往下走。主要分为以下几个步骤

  1. 停止当前的滚动
  2. 停止所有item动画(mItemAnimator.endAnimations()),ItemAnimator的默认是DefaultItemAnimator,这个可以放在以后研究下
  3. 移除所有子view,并回收
  4. 移除和回收所有scrape view
  5. 清除所有放在Recycler中的view
  6. 如果RecyclerView已经attach到window就调用dispatchDetachedFromWindow方法
  7. 将旧的Layoutmanager的RecyclerView设置null
  8. 调用childhelper的removeAllViewsUnfiltered方法(其实就是从RecyclerView中移除所有child)
  9. 将新的layout赋值给mLayout
  10. 将RecyclerView赋值给新的Layoutmanager并调用新的Layoutmanager的dispatchAttachedToWindow方法。
  11. 调用mRecycler.updateViewCacheSize()并调用requestLayout

    注意,上面1-8步的动作都是在旧的layoutmanager上完成。

我们一个一个方法看下去,谁叫这是真正的源码学习呢。。。

    public void stopScroll() {setScrollState(SCROLL_STATE_IDLE);stopScrollersInternal();}
     void setScrollState(int state) {if (state == mScrollState) {return;}if (DEBUG) {Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState,new Exception());}mScrollState = state;if (state != SCROLL_STATE_SETTLING) {stopScrollersInternal();}dispatchOnScrollStateChanged(state);}

先看setScrollState方法,首先会把mScrollState的值设置为SCROLL_STATE_IDLE(指当前view没有滑动),然后如果看到state不等于SCROLL_STATE_SETTLING就调用stopScrollersInternal(),明显我们这里就不是SCROLL_STATE_SETTLING,所以调用stopScrollersInternal()方法,stopScrollersInternal()方法如下

    private void stopScrollersInternal() {mViewFlinger.stop();if (mLayout != null) {mLayout.stopSmoothScroller();}}

就是调用ViewFlinger的stop方法,并调用LayoutManager的stopSmoothScroller()方法,ViewFlinger的方法如下

    public void stop() {removeCallbacks(this);mScroller.abortAnimation();}

首先要知道ViewFlinger是一个Runnable,主要处理RecyclerView的快速滑动,removeCallbacks(this)就是将此runnable从UI线程的消息队列中移除,而mScroller是一个OverScroll,abortAnimation方法就是直接将状态设置到动画的结束值。这个还是比较简单。我们来看下LayoutManager的stopSmoothScroller方法,其实里面调用了mSmoothScroller.stop(),而这个SmoothScroller.stop()方法内部如下

  protected final void stop() {if (!mRunning) {return;}onStop();mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION;mTargetView = null;mTargetPosition = RecyclerView.NO_POSITION;mPendingInitialRun = false;mRunning = false;// trigger a cleanupmLayoutManager.onSmoothScrollerStopped(this);// clear references to avoid any potential leak by a custom smooth scrollermLayoutManager = null;mRecyclerView = null;}

大致意思是将资源都清空了,相当于清理工作,关注两个地方,1. onStop()这个方法是个抽象方法,需要子类去实现,也就是LayoutManager的子类,主要是做一个清理工作。2.mLayoutManager.onSmoothScrollerStopped(this)这个方法内部就是将LayoutManager的mSmoothScroller变量设置为null,其实也是清理工作。
再回到setScrollState方法,接下来就是跑dispatchOnScrollStateChanged(state),代码如下

    void dispatchOnScrollStateChanged(int state) {// Let the LayoutManager go first; this allows it to bring any properties into// a consistent state before the RecyclerView subclass responds.if (mLayout != null) {mLayout.onScrollStateChanged(state);}// Let the RecyclerView subclass handle this event next; any LayoutManager property// changes will be reflected by this time.onScrollStateChanged(state);// Listeners go last. All other internal state is consistent by this point.if (mScrollListener != null) {mScrollListener.onScrollStateChanged(this, state);}if (mScrollListeners != null) {for (int i = mScrollListeners.size() - 1; i >= 0; i--) {mScrollListeners.get(i).onScrollStateChanged(this, state);}}}

就是调用各种监听scroll状态的listener,顺序如下

  1. LayoutManager的onScrollStateChanged
  2. RecyclerView子类的onScrollStateChanged
  3. 通过RecyclerView的setOnScrollListener设置的listener(此方法已经废弃)
  4. 通过RecyclerView的addOnScrollListener方法设置的listener
    到这里,setScrollState方法就跑完了,我们再回到stopScroll()方法
     public void stopScroll() {setScrollState(SCROLL_STATE_IDLE);stopScrollersInternal();}

可以看到又一次调用了stopScrollersInternal(),日了狗了,既然这里肯定会调用一次,为什么再setScrollState还要调用一次?这样应该会在某些情况下导致scrollListener被调用两次吧,不管了,继续往下。
在调用完stopScroll()后,setLayoutManager方法接下来调用以下代码

        if (mLayout != null) {// end all running animationsif (mItemAnimator != null) {mItemAnimator.endAnimations();}mLayout.removeAndRecycleAllViews(mRecycler);mLayout.removeAndRecycleScrapInt(mRecycler);mRecycler.clear();if (mIsAttached) {mLayout.dispatchDetachedFromWindow(this, mRecycler);}mLayout.setRecyclerView(null);mLayout = null;} else {mRecycler.clear();}

首先会判断mLayout是不是null,这个mLayout指的是旧的Layoutmanager,我们就当做他不是null,正常情况下第一次运行肯定为null,但这里我们从学习的目的出发,还是要知道,在有旧的Layoutmanager的时候,怎么处理。
首先是调用mItemAnimator.endAnimations(),此方法的作用是立即结束所有item的动画,并将相关property的值直接设置到结束值,ItemAnimator是个抽象类,关于ItemAnimator的解析可以参考如下文章RecylerView源码解析之ItemAnimator。

接下来是调用mLayout.removeAndRecycleAllViews(mRecycler)方法,这个方法最终会调到如下方法

 public void removeAndRecycleViewAt(int index, Recycler recycler) {final View view = getChildAt(index);removeViewAt(index);recycler.recycleView(view);}

其中,removeViewAt(index)最终会调到RecyclerView的removeViewAt(index)方法,也就是ViewGroup的removeViewAt方法,这里就不再分析了。我们主要看一下recycler.recycleView(view)方法,源码如下

 public void recycleView(View view) {// This public recycle method tries to make view recycle-able since layout manager// intended to recycle this view (e.g. even if it is in scrap or change cache)ViewHolder holder = getChildViewHolderInt(view);if (holder.isTmpDetached()) {removeDetachedView(view, false);}if (holder.isScrap()) {holder.unScrap();} else if (holder.wasReturnedFromScrap()) {holder.clearReturnedFromScrapFlag();}recycleViewHolderInternal(holder);}

首先通过isTmpDetached方法判断view是否detached,其实内部就是判断viewholder的mFlags有没有设置FLAG_TMP_DETACHED这个标志为,如果设置了,说明当前view已经被detach,我们这里假设返回true,则进到removeDetachedView方法,该方法如下:

    @Overrideprotected void removeDetachedView(View child, boolean animate) {ViewHolder vh = getChildViewHolderInt(child);if (vh != null) {if (vh.isTmpDetached()) {vh.clearTmpDetachFlag();} else if (!vh.shouldIgnore()) {throw new IllegalArgumentException("Called removeDetachedView with a view which"+ " is not flagged as tmp detached." + vh + exceptionLabel());}}// Clear any android.view.animation.Animation that may prevent the item from// detaching when being removed. If a child is re-added before the// lazy detach occurs, it will receive invalid attach/detach sequencing.child.clearAnimation();dispatchChildDetached(child);super.removeDetachedView(child, animate);}

该方法是ViewGroup的方法,RecyclerView有重写这个方法,主要完成的事情是:

  • 复位了mFlags中FLAG_TMP_DETACHED
  • 清空当前child view的动画
  • 分发dispatchChildDetached事件,分别会调用RecyclerView的onChildDetachedFromWindow方法,Adapter的onViewDetachedFromWindow方法和通过RecyclerView的addOnChildAttachStateChangeListener添加的listener。
  • 调用ViewGroup的removeDetachedView方法。

接下里会判断当前child view是不是scrap的,如果是就将它设为非scrap,如果不是,则判断是不是wasReturnedFromScrap,如果是,则清空FLAG_RETURNED_FROM_SCRAP标志位,这里先不讲这些标志是如何设置的。

然后代码会走到recycleViewHolderInternal(holder)

void recycleViewHolderInternal(ViewHolder holder) {
...final boolean transientStatePreventsRecycling = holder.doesTransientStatePreventRecycling();
final boolean forceRecycle = mAdapter != null&& transientStatePreventsRecycling&& mAdapter.onFailedToRecycleView(holder);if (forceRecycle || holder.isRecyclable()) {if (mViewCacheMax > 0&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID| ViewHolder.FLAG_REMOVED| ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {// Retire oldest cached viewint cachedViewSize = mCachedViews.size();if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {recycleCachedViewAt(0);cachedViewSize--;}int targetCacheIndex = cachedViewSize;if (ALLOW_THREAD_GAP_WORK&& cachedViewSize > 0&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {// when adding the view, skip past most recently prefetched viewsint cacheIndex = cachedViewSize - 1;while (cacheIndex >= 0) {int cachedPos = mCachedViews.get(cacheIndex).mPosition;if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {break;}cacheIndex--;}targetCacheIndex = cacheIndex + 1;}mCachedViews.add(targetCacheIndex, holder);cached = true;}if (!cached) {addViewHolderToRecycledViewPool(holder, true);recycled = true;}} else {// NOTE: A view can fail to be recycled when it is scrolled off while an animation// runs. In this case, the item is eventually recycled by// ItemAnimatorRestoreListener#onAnimationFinished.// TODO: consider cancelling an animation when an item is removed scrollBy,// to return it to the pool fasterif (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"+ exceptionLabel());}}// even if the holder is not removed, we still call this method so that it is removed// from view holder lists.mViewInfoStore.removeViewHolder(holder);if (!cached && !recycled && transientStatePreventsRecycling) {holder.mOwnerRecyclerView = null;}}

直接从重点部分开始,先通过if (forceRecycle || holder.isRecyclable())判断当前view是否可回收,这里的forceRecycle默认是false,所以只要看holder.isRecyclable()的值, 我们这里先把它看做可回收,至于具体什么情况下可回收,后面的博客再讲。接下来再判断mViewCacheMax是否大于0,默认情况下这个值是2,这里说明一下,那些被回收的View会被放到一个RecycledViewPool中,这个pool中的view是可以被其他RecyclerView的实例使用的,而把view放进这个pool的前提是mCachedViews这个list已经被塞满,否则会先放到这个list中,而这个list的容量是mViewCacheMax决定的。再回到代码,当mViewCacheMax大于0且
holderflag由不包含ViewHolder.FLAG_INVALID|ViewHolder.FLAG_REMOVED |ViewHolder.FLAG_UPDATE|ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN这些值的时候,就可以开始回收工作了。
首先会判断mCachedViews有没有被塞满,如果满的话,就将第一个view放进pool中,并将第一个view中mCachedViews中移除。这样mCachedViews就空出了一个位置,可以把我们要cache的view放进去,接下来要跑的这一段先不讲,因为涉及到预抓取,后面的博客再讲。这段代码的作用就是决定view要插入到mCachedViews的哪个位置,反正默认是插入到尾部,我们这里就当做它插入到了尾部。

   if (ALLOW_THREAD_GAP_WORK&& cachedViewSize > 0&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {// when adding the view, skip past most recently prefetched viewsint cacheIndex = cachedViewSize - 1;while (cacheIndex >= 0) {int cachedPos = mCachedViews.get(cacheIndex).mPosition;if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {break;}cacheIndex--;}targetCacheIndex = cacheIndex + 1;}

接下来会判断是否有cache成功,如果没有cache成功,则直接放到pool中

 if (!cached) {addViewHolderToRecycledViewPool(holder, true);recycled = true;}

没有cache的情况就是刚开始的这段返回了false。

  if (mViewCacheMax > 0&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID| ViewHolder.FLAG_REMOVED| ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN))

最后调用removeViewHolder(),将此holder从mOldChangedHoldersmLayoutHolderMap中删除,如果前面cache和recycle都失败,且transientStatePreventsRecyclingtrue,则将holder.mOwnerRecyclerView设为null,表示没有回收成功,transientStatePreventsRecycling这个变量是通过holder.doesTransientStatePreventRecycling()确定的,源码如下

        /*** @return True if ViewHolder is not referenced by RecyclerView animations but has* transient state which will prevent it from being recycled.*/private boolean doesTransientStatePreventRecycling() {return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && ViewCompat.hasTransientState(itemView);}

从注释可以看出其实就是判断itemview是不是hasTransientState,关于TransientState我也不清楚,应该是表示有动画正在进行,但这个动画又不是ItemAnimation指定的动画,先放着。

到这里setLayoutManager方法中的mLayout.removeAndRecycleAllViews(mRecycler)就讲完了,接下来是mLayout.removeAndRecycleScrapInt(mRecycler),这里就不再分析了,因为逻辑很简单,和上面的removeAndRecycleAllViews逻辑差不多。同学们可以自己去看。

再回到setLayoutManagre,接下来就是调用mRecycler.clear(),代码如下

        public void clear() {mAttachedScrap.clear();recycleAndClearCachedViews();}

就是把mAttachedScrap清空,这个mAttachedScrap就是存放scrapview的地方,然后再把mCachedViews中的view放到pool中,最后清空mCachedViews,就是这么简单。setLayoutManager接下来来调用的代码也很简单,就是把新的LayoutManager赋值给mLayout,并将RecyclerView的引用赋值给新的LayoutManager,并更新·mCacheViews·的size, 我们这边重点来看一下mRecycler.updateViewCacheSize()方法。

 void updateViewCacheSize() {int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;mViewCacheMax = mRequestedCacheMax + extraCache;// first, try the views that can be recycledfor (int i = mCachedViews.size() - 1;i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {recycleCachedViewAt(i);}}

可以看到mCachedViews的最大size由mViewCacheMax决定,而mViewCacheMax的size则由mRequestedCacheMaxmLayout.mPrefetchMaxCountObserved两者决定mRequestedCacheMax可以通过RecyclerView的setItemViewCacheSize方法设定,默认值是2,而mLayout.mPrefetchMaxCountObserved是由GapWorker决定,暂时先不讲,因为我自己也不知道。。。哈哈。updateViewCacheSize()中,如果发现新的maxsize小于旧的size,则会把多出的那几个cacheview放进pool中。

最后setLayoutManager方法调用requestLayout方法,进行重新布局。

好了,setLayoutManager方法就分析到这里,里面有些地方我暂时还不清楚,比如GapWorker,等后面学习的深入再回来补上。也有可能有讲的不对的地方,还请同学们提出。

RecyclerView源码学习笔记(一)构造函数和setLayoutManager方法相关推荐

  1. Vuex 4源码学习笔记 - 通过dispatch一步步来掌握Vuex整个数据流(五)

    在上一篇笔记中:Vuex 4源码学习笔记 - Store 构造函数都干了什么(四) 我们通过查看Store 构造函数的源代码可以看到主要做了三件事情: 初始化一些内部变量以外 执行installMod ...

  2. Java多线程之JUC包:Semaphore源码学习笔记

    若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5625536.html Semaphore是JUC ...

  3. Vuex 4源码学习笔记 - Vuex是怎么与Vue结合?(三)

    在上一篇笔记中:Vuex源码学习笔记 - Vuex开发运行流程(二) 我们通过运行npm run dev命令来启动webpack,来开发Vuex,并在Vuex的createStore函数中添加了第一个 ...

  4. Apache log4j-1.2.17源码学习笔记

    (1)Apache log4j-1.2.17源码学习笔记 http://blog.csdn.net/zilong_zilong/article/details/78715500 (2)Apache l ...

  5. RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?

    RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 文章目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目 ...

  6. Vuex 4源码学习笔记 - 通过Vuex源码学习E2E测试(十一)

    在上一篇笔记中:Vuex 4源码学习笔记 - 做好changelog更新日志很重要(十) 我们学到了通过conventional-changelog来生成项目的Changelog更新日志,通过更新日志 ...

  7. jquery源码学习笔记三:jQuery工厂剖析

    jquery源码学习笔记二:jQuery工厂 jquery源码学习笔记一:总体结构 上两篇说过,query的核心是一个jQuery工厂.其代码如下 function( window, noGlobal ...

  8. 雷神FFMpeg源码学习笔记

    雷神FFMpeg源码学习笔记 文章目录 雷神FFMpeg源码学习笔记 读取编码并依据编码初始化内容结构 每一帧的视频解码处理 读取编码并依据编码初始化内容结构 在开始编解码视频的时候首先第一步需要注册 ...

  9. PHP Yac cache 源码学习笔记

    YAC 源码学习笔记 本文地址 http://blog.csdn.net/fanhengguang_php/article/details/54863955 config.m4 检测系统共享内存支持情 ...

最新文章

  1. 21个高质量的Swift开源iOS App
  2. java版spring cloud+spring boot+redis多租户社交电子商务平
  3. Android 之 LogDog
  4. python学习之wxPython
  5. 7.1.8860.142
  6. Python压缩文件夹/解压缩zip文件
  7. 如何在C#中获取Unix时间戳
  8. 试用了GIMP的Smart remove selection,结果有些失望啊,哈哈
  9. 《Outlier Analysis》书籍
  10. wordpress目录文件结构
  11. Office application 版本
  12. 我的世界java版本试玩_我的世界Minecraft Java版17w49a发布
  13. VMware vSphere重置系统配置
  14. 搭建DM两节点mpp集群
  15. python递归函数例子_Python递归函数经典案例-汉诺塔问题
  16. 基于STM32楼梯层控制系统
  17. Java编写 输入一个字符串,请编写一个函数统计连续相同字符及其数量。例如,输入“aabbbbcccAB”,返回“a2b4c3 A1 B1”及5;
  18. android-历史版本
  19. showModalDialog传值
  20. python pop3lib连接网易企业邮箱

热门文章

  1. PID串口助手的第一部分:串口通信
  2. 两步实现安卓手机秒变网络摄像头
  3. 冬季送暖!实用围巾打法!(图)
  4. 新的3D地图制图技术改变了全球定位的游戏规则
  5. 二十三、正则表达式中的“r”含义
  6. 车载系统升级、“特饭”会员品牌上线,解读新特背后的互联网产品逻辑...
  7. ad19电气规则检查_AD19中PCB设计常用规则-电气规则设置
  8. win7资源管理器中输入ftp站点跳转到浏览器
  9. vue项目中运行项目造成浏览器崩溃
  10. 运动装备品牌排行榜,运动爱好者必备好物分享