LJ的Blog

学海无涯苦做舟

0%

探究RecyclerView的ViewHolder复用

啥是RecyclerView

  • A flexible view for providing a limited window into a large data set.
    一个在大小有限的窗口内展示大量数据集的view。恩,我的翻译一向不咋滴。。所以原文也放上了。

RecyclerView网上很多文都说是用来取代ListView和GridView的,事实上RecyclerView的确可以做到ListView和GridView能做的事,而且他将ViewHolder和Adapter都作为内部类,写在了RecyclerView中。先不管这把所有类都写在RecyclerView内部的做法是否好,但是ViewHolder作为RecyclerView内部复用的单位,直接避免了不必要的findViewById,而在ListView中则需要我们自己定义ViewHolder。

一个使用RecyclerView的示例

在进行探究之前,首先回顾一下我们是如何使用一个RecyclerView的。
第一步在布局文件里加上RecyclerView:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<android.support.v7.widget.RecyclerView
android:id="@+id/rv_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

第二步,给RecyclerView的item编写布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="8dp">

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/test" />

</LinearLayout>

第三步,为RecyclerView写一个Adapter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.xiasuhuei321.test;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.support.v7.widget.RecyclerView.ViewHolder;

/**
* Created by xiasuhuei321 on 2016/12/25.
* author:luo
* e-mail:xiasuhuei321@163.com
*/

public class TestAdapter extends RecyclerView.Adapter {
Context mContext;

public TestAdapter(Context context) {
this.mContext = context;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

return new ItemViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_test, parent, false));
}

@Override
public int getItemCount() {
return 100;
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
}

class ItemViewHolder extends ViewHolder {

public ItemViewHolder(View itemView) {
super(itemView);
}
}

}

这里只是简单的演示,代码写的都非常的简单。。各位都不要模仿。。。

第四步,给RecyclerView设置对应的布局和Adapter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.xiasuhuei321.test;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

/**
* Created by xiasuhuei321 on 2016/12/25.
* author:luo
* e-mail:xiasuhuei321@163.com
*/

public class TestActivity extends AppCompatActivity {

private RecyclerView mList;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);

mList = (RecyclerView) findViewById(R.id.rv_list);
mList.setLayoutManager(new LinearLayoutManager(this));
mList.setAdapter(new TestAdapter(this));
}
}

最后看下效果

呆毛王

通过以上的流程,对RecyclerView的简单使用就过完了,在这个流程中,可以看出编写Adapter是一个关键,事实上RecyclerView和ListView都一样,都是通过Adapter来设置和管理每一个item的。

ViewHolder与复用

在复写RecyclerView.Adapter的时候,需要我们复写两个方法:

  • onCreateViewHolder
  • onBindViewHolder

这两个方法从字面上看就是创建ViewHolder和绑定ViewHolder的意思,来看一下源码中对我们实现的这两个方法的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
* an item.
* <p>
* This new ViewHolder should be constructed with a new View that can represent the items
* of the given type. You can either create a new View manually or inflate it from an XML
* layout file.
* <p>
* The new ViewHolder will be used to display items of the adapter using
* {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display
* different items in the data set, it is a good idea to cache references to sub views of
* the View to avoid unnecessary {@link View#findViewById(int)} calls.
*
* @param parent The ViewGroup into which the new View will be added after it is bound to
* an adapter position.
* @param viewType The view type of the new View.
*
* @return A new ViewHolder that holds a View of the given view type.
* @see #getItemViewType(int)
* @see #onBindViewHolder(ViewHolder, int)
*/

当RecyclerView需要一个新的类型的item的ViewHolder的时候调用这个方法。

第二段描述是讲如何创建这个ViewHolder,跳过。

新的ViewHolder将会被用来通过adapter调用onBindViewHolder展示item。由于它将会被复用去展示在数据集中的不同items,所以缓存View的子view引用去避免不必要的对findViewById方法的调用是一个好主意。

看了上面的这段话,我产生了一个疑问,第一段话的意思仿佛是只有在需要新的类型的ViewHolder的时候才需要调用这个方法。如果是这样,的确可以从侧面说明他是以ViewHolder为单位来实现复用的。为了验证我的想法,我在onCreateViewHolder和onBindViewHolder方法中加入了计数的代码,看一下log:

log

从中可以看出并不是像我想的那样,只调用了一次,稍微想一下也很容易想明白,因为他是通过ViewHolder复用不假,我这里只有一种ViewType,上下滑动的时候需要的ViewHolder种类是只有一种,但是需要的ViewHolder对象数量并不止一个。所以在后面创建了5个ViewHolder之后,需要的数量够了,无论我怎么滑动,他都只需要复用以前创建的对象就行了。

在这里,感觉ViewHolder的类型和对象数量有点像Java中Class和对象的关系。Java中第一次将.class装载入JVM虚拟机的时候,会生成一个Class对象,以后所有这个类的对象都由Class生成。是不是有点像呢?

看到了这个log之后,我的第一反应是在这个ViewHolder对象的数量“够用”之后就停止调用onCreateViewHolder方法,看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new
* {@link ViewHolder} and initializes some private fields to be used by RecyclerView.
*
* @see #onCreateViewHolder(ViewGroup, int)
*/
public final VH createViewHolder(ViewGroup parent, int viewType) {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
TraceCompat.endSection();
return holder;
}

可以发现这里并没有限制,那么是不是在调用这个createViewHolder方法的时候做了限制呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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;
}

可以看出的确是有条件的。当然,在此不具体分析,不然可能会深入细节无法自拔。

Recycler && RecycledViewPool

说实话,上面分析完,我也有点没方向,因为毕竟整个RecyclerView一万多行代码在那,不知道看哪了,不过好在网上有篇文曾干过和我差不多的事RecyclerView源码分析,前人指了条路,跟着看一下源码好了。

Recycler:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* A Recycler is responsible for managing scrapped or detached item views for reuse.
*
* <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but
* that has been marked for removal or reuse.</p>
*
* <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for
* an adapter's data set representing the data at a given position or item ID.
* If the view to be reused is considered "dirty" the adapter will be asked to rebind it.
* If not, the view can be quickly reused by the LayoutManager with no further work.
* Clean views that have not {@link android.view.View#isLayoutRequested() requested layout}
* may be repositioned by a LayoutManager without remeasurement.</p>
*/

Recycler负责管理废弃(scrapped)或者分离(detach)的item。

scrapped指的是仍然在RecyclerView上但是已经被标记了移除或者复用。

一个对Recycler的经典的使用时LayoutManager,它通过Recycler为adapter的数据集的特定位置获取一个view。如果这个view将被复用但被认为是“dirty”的,那么这个adapter将调用方法重新绑定它。如果不是,这个view可以迅速的被LayoutManager复用而不用进一步的处理。Clean view无需调用request layout,不需要重新测量就能复用。

RecycledViewPool:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* RecycledViewPool lets you share Views between multiple RecyclerViews.
* <p>
* If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
* and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
* <p>
* RecyclerView automatically creates a pool for itself if you don't provide one.
*
*/
public static class RecycledViewPool {
private SparseArray<ArrayList<ViewHolder>> mScrap =
new SparseArray<ArrayList<ViewHolder>>();
private SparseIntArray mMaxScrap = new SparseIntArray();
private int mAttachCount = 0;

private static final int DEFAULT_MAX_SCRAP = 5;

public void clear() {
mScrap.clear();
}

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

int size() {
int count = 0;
for (int i = 0; i < mScrap.size(); i ++) {
ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i);
if (viewHolders != null) {
count += viewHolders.size();
}
}
return count;
}

public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList scrapHeap = getScrapHeapForType(viewType);
if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
scrap.resetInternal();
scrapHeap.add(scrap);
}

void attach(Adapter adapter) {
mAttachCount++;
}

void detach() {
mAttachCount--;
}


/**
* Detaches the old adapter and attaches the new one.
* <p>
* RecycledViewPool will clear its cache if it has only one adapter attached and the new
* adapter uses a different ViewHolder than the oldAdapter.
*
* @param oldAdapter The previous adapter instance. Will be detached.
* @param newAdapter The new adapter instance. Will be attached.
* @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same
* ViewHolder and view types.
*/
void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
boolean compatibleWithPrevious) {
if (oldAdapter != null) {
detach();
}
if (!compatibleWithPrevious && mAttachCount == 0) {
clear();
}
if (newAdapter != null) {
attach(newAdapter);
}
}

private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
ArrayList<ViewHolder> scrap = mScrap.get(viewType);
if (scrap == null) {
scrap = new ArrayList<>();
mScrap.put(viewType, scrap);
if (mMaxScrap.indexOfKey(viewType) < 0) {
mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
}
}
return scrap;
}
}

老规矩,先看注释:RecycledViewPool让你在多个RecyclerView之间共享View。如果你想要在RecyclerView间循环利用view,创建一个RecyclerViewPool的实例然后调用setRecyclerViewPool方法。

如果你不提供一个那么RecyclerView将为他自己自动的创建一个RecycledViewPool。

接下来看下里面的代码,内部有一个SparseArray一个SparseIntArray,看到这终于感觉快看到点子上了,毕竟看起来就像是两个放东西的容器,应该是离真相不远了。先看下SparseArray是个啥,点进去看注释第一句就是 SparseArrays map integers to Objects ,这是一个integer和对象的映射,他内部也是有两个数组一个是integer作为键的int[] mKeys的int类型的数组,另外一个是Object[] mValues的对象数组。而结合他在RecycledViewPool中的定义 SparseArray<ArrayList> mScrap;这种定义,表明是一个integer映射ViewHolder的集合。这个该怎么理解呢?在我们的实际使用中,很可能会有非常多种类的viewType,那么这个时候同一类的ViewHolder就保存在同一个ArrayList中,而在RecyclerView内部ViewType都是通过int类型的数字来代表的,正好符合。由此可以大致可以确定这个mScrap就是保存ViewHolder的关键了。

而SparseIntArray则是Integer映射Integer,在这可以结合setMaxRecycledViews方法中的第一行代码 mMaxScrap.put(viewType,max),可以看出这是表明了一种ViewType对应的可保存对象集合的最大尺寸。

大致了解了下RecycledViewPool,然后回头去看一下之前被我跳过的getViewForPosition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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;
}

在具体分析这个方法之前,先给出这个类内部几个参数的大致意思:
private ArrayList mAttachedScrap
private ArrayList mChangedScrap 与RecyclerView分离的ViewHolder列表。
private ArrayList mCachedViews ViewHolder缓存列表。
private ViewCacheExtension mViewCacheExtension 开发者控制的ViewHolder缓存
private RecycledViewPool mRecyclerPool 提供复用ViewHolder池。

可以看到源码中已经给了我们步骤提示:

  • If there is a changed scrap, try to find from there
    从mChangedScrap中寻找ViewHolder
    1. Find from scrap by position
      如果上一步未找到ViewHolder,则从mAttachedScrap中通过position找
    1. Find from scrap via stable ids, if exists
      如果上一步未找到且存在stable id,则通过id在mAttachedScrap中找ViewHolder
  • 如果上一步未找到且mViewCacheExtension不为空,则在mViewCacheExtension中找ViewHolder
  • 如果上一步未找到则通过RecycledCiewPool寻找ViewHolder
  • 如果上一步未找到则通过Adapter的createViewHolder创建一个新的ViewHolder

如此一来经历了以上的步骤,一个ViewHolder便会先从缓存中读取,如果都无法匹配到,则会新创建一个。如此便实现了ViewHolder的复用。

后记

无关技术,自己的小结,各位可以跳过~
最近有点小忙,而且也一直在看Java的一些东西,处于积累的阶段吧,所以文章更新的没以前勤快了。不过我还是希望自己能够坚持写下去,这次只阅读了RecyclerView的部分源码,感觉自己还有很多的不足,以后还会继续探究RecyclerView的其他东西。