RecyclerView 的常用方法;工作原理與ListView比較;原始碼解析
寫在前面
本文原創,轉載請以連結形式註明地址:http://kymjs.com/code/2016/07/10/01起深入淺出這名字的時候我是慎重又慎重的,生怕被人罵標題黨,寫的什麼破玩意還敢說深入淺出。所以還是請大家不要抱著太高的期望,因為沒有期望就沒有失望,就像陳潤說的,超預期嘛。全當看小說的心情來看這系列文章了。
這篇文章分三個部分,簡單跟大家講一下 RecyclerView 的常用方法與奇葩用法;工作原理與ListView比較;原始碼解析;
常用方法
RecyclerView 與 ListView、GridView 類似,都是可以顯示同一種類型 View 的集合的控制元件。
首先看看最簡單的用法,四步走:
0.接入build.gradle 檔案中加入
compile 'com.android.support:recyclerview-v7:24.0.0'
1.建立物件
RecyclerView recyclerview = (RecyclerView)
findViewById(R.id.recyclerview);
2.設定顯示規則
recyclerview.setLayoutManager(new LinearLayoutManager(
this, LinearLayoutManager.VERTICAL, false));
RecyclerView 將所有的顯示規則交給一個叫 LayoutManager 的類去完成了。
LayoutManager
3.設定介面卡
recyclerview.setAdapter(adapter);
介面卡,同 ListView 一樣,用來設定每個item顯示內容的。
通常,我們寫 ListView 介面卡,都是首先繼承 BaseAdapter,實現四個抽象方法,建立一個靜態ViewHolder
而 RecyclerView 也是類似的步驟,首先繼承
RecyclerView.Adapter<VH>
類,實現三個抽象方法,建立一個靜態的ViewHolder。不過
RecyclerView 的 ViewHolder 建立稍微有些限制,類名就是上面繼承的時候泛型中宣告的類名(好像反了,應該是上面泛型中的類名應該是這個holder的類名);並且ViewHolder 必須繼承自RecyclerView.ViewHolder
類。
public class DemoAdapter extends RecyclerView.Adapter<DemoAdapter.VH> {
private List<Data> dataList;
private Context context;
public DemoAdapter(Context context, ArrayList<Data> datas) {
this.dataList = datas;
this.context = context;
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
return new VH(View.inflate(context, android.R.layout.simple_list_item_2, null));
}
@Override
public void onBindViewHolder(VH holder, int position) {
holder.mTextView.setText(dataList.get(position).getNum());
}
@Override
public int getItemCount() {
return dataList.size();
}
public static class VH extends RecyclerView.ViewHolder {
TextView mTextView;
public VH(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(android.R.id.text1);
}
}
}
更多方法
除了常用方法,當然還有不常用的。
- 瀑布流與滾動方向
前面已經介紹過,RecyclerView
實現瀑布流,可以通過一句話設定:recycler.setLayoutManager(new StaggeredGridLayoutManager(2, VERTICAL))
就可以了。
其中 StaggeredGridLayoutManager 第一個引數表示列數,就好像 GridView 的列數一樣,第二個引數表示方向,可以很方便的實現橫向滾動或者縱向滾動。
使用 demo 可以檢視:Github 【RecyclerView簡單使用】
- 新增刪除 item 的動畫
同 ListView 每次修改了資料來源後,都要呼叫 notifyDataSetChanged()
重新整理每項 item 類似,只不過RecyclerView 還支援區域性重新整理
notifyItemInserted(index);
、notifyItemRemoved(position)
、notifyItemChanged(position)
。
在新增或刪除了資料後,RecyclerView 還提供了一個預設的動畫效果,來改變顯示。同時,你也可以定製自己的動畫效果:模仿
DefaultItemAnimator 或直接繼承這個類,實現自己的動畫效果,並呼叫recyclerview.setItemAnimator(new DefaultItemAnimator());
設定上自己的動畫。
使用 demo 可以檢視:Github 【RecyclerView預設動畫】
- LayoutManager的常用方法
findFirstVisibleItemPosition()
返回當前第一個可見 Item 的 positionfindFirstCompletelyVisibleItemPosition()
返回當前第一個完全可見 Item 的 positionfindLastVisibleItemPosition()
返回當前最後一個可見 Item 的 positionfindLastCompletelyVisibleItemPosition()
返回當前最後一個完全可見 Item 的 position.scrollBy()
滾動到某個位置。
- adapter封裝
其實很早之前寫過一篇關於 RecyclerView 介面卡的封裝,所以這不再贅述了,傳送門:RecyclerView的通用介面卡
使用 demo 可以檢視:Github 【RecyclerView通用介面卡演示】
吐槽
- OnItemTouchListener 什麼鬼?
用習慣了 ListView 的 OnItemClickListener ,RecyclerView 你的OnItemClickListener 呢?
Tell me where do I find, something like ListView listener ?
好吧,翻遍了 API 列表,就找到了個 OnItemTouchListener
,這特麼什麼鬼,我幹嘛要對每個 item 監聽觸控式螢幕事件。
萬萬沒想到,最終我還是在 Google IO 裡面的介紹找到了原因。原來是 Google 的工程師分不清究竟是改給 listview 的 item 新增點選事件,還是應該給每個 item 的 view 新增點選事件,索性就不給OnItemClickListener 了,然後在 support demo 裡面,你就會發現,RecyclerView 的 item 點選事件都是寫在了 adapter 的 ViewHolder 裡面。
當然,除了 support demo 包裡面使用的在 ViewHolder 裡面設定點選事件以外,我還寫好了一個 RecyclerView 使用的OnItemClickListener 程式碼請見:RecyclerItemClickListener.java
需要一提的是,網上有很多這種類似的 ItemClickListener ,在使用的時候一定注意一個問題,就是迴圈引用問題。比如 listener 裡面持有了一個 recyclerview, 而這個 recyclerview 在呼叫 setListener() 的時候又持有了一個 listener。儘管 Java 虛擬機器現在可以解決這種問題了,但作為程式碼編寫者,這種寫法還是應該儘量避免的。
- divider 跑哪了?
在ListView
中設定 divider 非常簡單,只需要在 XML 檔案中設定就可以了,同時還可以設定divider 高度。
android:divider="@android:color/black"
android:dividerHeight="2dp"
而在RecyclerView
裡面,想實現這兩種需求,稍微複雜一點,需要自己繼承RecyclerView.ItemDecoration
來實現想要實現的方法。
雖說這樣寫靈活多了,但是要額外寫一個類去做難免麻煩,這裡大家可以看我已經實現好的一個封裝,包括顯示純色divider、顯示圖片divider、divider的上下左右的間距、寬高設定 應該可以滿足基本需求了:Divider.java
使用 demo 可以檢視:Github 【自定義 Divider 使用】
五虎上將工作原理
其實上圖中並沒有寫完整,大 boss RecyclerView 應該有這五虎上將:
類名 | 作用 |
---|---|
RecyclerView.LayoutManager | 負責Item檢視的佈局的顯示管理 |
RecyclerView.ItemDecoration | 給每一項Item檢視新增子View,例如可以進行畫分隔線之類 |
RecyclerView.ItemAnimator | 負責處理資料新增或者刪除時候的動畫效果 |
RecyclerView.Adapter | 為每一項Item建立檢視 |
RecyclerView.ViewHolder | 承載Item檢視的子佈局 |
LayoutManager工作原理
java.lang.Object
↳ android.view.View
↳ android.view.ViewGroup
↳ android.support.v7.widget.RecyclerView
首先是 RecyclerView 繼承關係,可以看到,與 ListView 不同,他是一個 ViewGroup。既然是一個 View,那麼就不可少的要經歷onMeasure()
、onLayout()
、onDraw()
這三個方法。 實際上,RecyclerView
就是將onMeasure()
、onLayout()
交給了 LayoutManager 去處理,因此如果給 RecyclerView 設定不同的 LayoutManager 就可以達到不同的顯示效果,因為onMeasure()
、onLayout()
都不同了嘛。
ItemDecoration 工作原理
ItemDecoration 是為了顯示每個 item 之間分隔樣式的。它的本質實際上就是一個 Drawable。當 RecyclerView 執行到onDraw()
方法的時候,就會呼叫到他的
onDraw()
,這時,如果你重寫了這個方法,就相當於是直接在 RecyclerView 上畫了一個 Drawable 表現的東西。 而最後,在他的內部還有一個叫getItemOffsets()
的方法,從字面就可以理解,他是用來偏移每個 item 檢視的。當我們在每個 item 檢視之間強行插入繪畫了一段 Drawable,那麼如果再照著原本的邏輯去繪 item
檢視,就會覆蓋掉 Decoration 了,所以需要getItemOffsets()
這個方法,讓每個 item 往後面偏移一點,不要覆蓋到之前畫上的分隔樣式了。
ItemAnimator
每一個 item 在特定情況下都會執行的動畫。說是特定情況,其實就是在檢視發生改變,我們手動呼叫notifyxxxx()
的時候。通常這個時候我們會要傳一個下標,那麼從這個標記開始一直到結束,所有 item 檢視都會被執行一次這個動畫。
Adapter工作原理
首先是介面卡,介面卡的作用都是類似的,用於提供每個 item 檢視,並返回給 RecyclerView 作為其子佈局新增到內部。
但是,與 ListView 不同的是,ListView 的介面卡是直接返回一個 View,將這個 View 加入到 ListView 內部。而 RecyclerView 是返回一個 ViewHolder 並且不是直接將這個 holder 加入到檢視內部,而是加入到一個快取區域,在檢視需要的時候去快取區域找到 holder 再間接的找到 holder 包裹的 View。
ViewHolder
每個 ViewHolder 的內部是一個 View,並且 ViewHolder 必須繼承自RecyclerView.ViewHolder
類。這主要是因為 RecyclerView 內部的快取結構並不是像 ListView 那樣去快取一個 View,而是直接快取一個 ViewHolder ,在 ViewHolder 的內部又持有了一個 View。既然是快取一個
ViewHolder,那麼當然就必須所有的 ViewHolder 都繼承同一個類才能做到了。
快取與複用的原理
還是一張截圖
RecyclerView 的內部維護了一個二級快取,滑出介面的 ViewHolder 會暫時放到 cache 結構中,而從 cache 結構中移除的 ViewHolder,則會放到一個叫做RecycledViewPool 的迴圈快取池中。
順帶一說,RecycledView 的效能並不比 ListView 要好多少,它最大的優勢在於其擴充套件性。但是有一點,在 RecycledView 內部的這個第二級快取池RecycledViewPool是可以被多個 RecyclerView 共用的,這一點比起直接快取 View 的 ListView 就要高明瞭很多,但也正是因為需要被多個 RecyclerView 公用,所以我們的 ViewHolder 必須繼承自同一個基類(即RecyclerView.ViewHolder)。
預設的情況下,cache 快取 2 個 holder,RecycledViewPool 快取 5 個 holder。對於二級快取池中的 holder 物件,會根據 viewType 進行分類,不同型別的 viewType 之間互不影響。
原始碼解析
onMeasure
既然是一個 View,我們先從onMeasure()
開始看。
之前我們就說了 RecyclerView 的 measure 和 layout 都是交給了 LayoutManager 去做的,來看一下為什麼:
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);
} else {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
}
不論是否啟用 mAutoMeasure 最終都會執行到 mLayout.onMeasure() 方法中,而這個 mLayout 就是一個 LayoutManager 物件。
我們挑選 LinearLayoutManager 來看
發現它並沒有onMeasure()
方法,LinearLayoutManager 直接繼承自 LayoutManager,所以又回到了父類 LayoutManager 中。
void defaultOnMeasure(int widthSpec, int heightSpec) {
// calling LayoutManager here is not pretty but that API is already public and it is better
// than creating another method since this is internal.
final int width = LayoutManager.chooseSize(widthSpec,
getPaddingLeft() + getPaddingRight(),
ViewCompat.getMinimumWidth(this));
final int height = LayoutManager.chooseSize(heightSpec,
getPaddingTop() + getPaddingBottom(),
ViewCompat.getMinimumHeight(this));
setMeasuredDimension(width, height);
}
有一句非常奇葩的註釋:在這裡直接呼叫 LayoutManager 靜態方法並不完美,因為本身就是在類內部,更好的辦法呼叫一個單獨的方法。但反正這段程式碼也已經公開了,你們自己看著辦。。。。。。
如果這不是歷史遺留問題,那肯定是臨時工寫的,你寫的時候都意識到這問題了,你還把一大堆類都寫在一個類裡面,造成了 RecyclerView 一個類有一萬多行程式碼。我猜你是為了類之間跨類呼叫方便一點,可是你就不能設定一個包訪問許可權,所有類成員方法都包內呼叫嗎,一個類幹了六個類的活,網上居然還有人說這是高內聚的表現。
接著是chooseSize()
方法,很簡單,直接根據測量值和模式返回了最適大小。
public static int chooseSize(int spec, int desired, int min) {
final int mode = View.MeasureSpec.getMode(spec);
final int size = View.MeasureSpec.getSize(spec);
switch (mode) {
case View.MeasureSpec.EXACTLY:
return size;
case View.MeasureSpec.AT_MOST:
return Math.min(size, Math.max(desired, min));
case View.MeasureSpec.UNSPECIFIED:
default:
return Math.max(desired, min);
}
}
緊接著是對子控制元件 measure ,呼叫了:dispatchLayoutStep2()
呼叫了相同的方法,子控制元件的 measure 在 layout 過程中講解
onLayout
然後我們來看 layout 過程. 在onLayout()
方法中間接的呼叫到了這麼一個方法:dispatchLayoutStep2()
,在它之中又呼叫到了mLayout.onLayoutChildren(mRecycler, mState);
我們重點看這個onLayoutChildren()
方法。
這個方法在 LayoutManager 中的實現是空的,那麼想必是在子類中實現了吧。還是找 LinearLayoutManager ,跟上面 measure 過程一樣,呼叫了dispatchLayoutStep2()
跟進去發現這麼一個方法:
fill(recycler, mLayoutState, state, false);
onLayoutChildren() 中有一個非常重要的方法:fill()
recycler,是一個全域性的回收複用池,用於對每個itemview回收以及複用提供支援。稍後會詳細講這個。
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunk(recycler, state, layoutState, layoutChunkResult);
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
}
fill() 作用就是根據當前狀態決定是應該從快取池中取 itemview 填充 還是應該回收當前的 itemview。
其中,layoutChunk() 負責從快取池 recycler 中取 itemview,並呼叫View.addView()
將獲取到的 ItemView 新增到 RecyclerView 中去,並呼叫 itemview 自身的 layout 方法去佈局 item 位置。
同時在這裡,還呼叫了measureChildWithMargins()
來測繪子控制元件大小以及設定顯示位置。這一步,我們到下面的 draw 過程還要講。
而這全部的新增邏輯都放在一個 while 迴圈裡面,不停的新增 itemview 到 recyclerview 裡面,直到塞滿所有可見區域為止。
onDraw
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
在 onDraw()
中,除了繪製自己以外,還多調了一個mItemDecorations 的 onDraw() 方法,這個mItemDecorations 就是前面吐槽的分隔線的集合。
之前在講 RecyclerView 的五虎上將的時候就講過這個 ItemDecoration。 當時我們還重寫了一個方法叫getItemOffsets()
目的是為了不讓 itemview 擋住分隔線。那他是在哪呼叫的呢?
還記得 layout 時說的那個measureChildWithMargins()
嗎,就是在這裡:
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
child.measure(widthSpec, heightSpec);
}
}
在 itemview measure 的時候,會把偏移量也計算進來,也就是說:其實 ItemDecoration 的寬高是計算在 itemview 中的,只不過 itemview 本身繪製區域沒有那麼大,留出來的地方正好的透明的,於是就透過 itemview 顯示出了 ItemDecoration。那麼就很有意思了,如果我故意在 ItemDecoration 的偏移量中寫成0,那麼 itemview 就會擋住 ItemDecoration,而在 itemview 的增加或刪除的時候,會短暫的消失(透明),這時候就又可以透過 itemview 看到 ItemDecoration 的樣子。使用這種組合還可以做出意想不到的動畫效果。
滾動
前面我們已經完整的走完了 RecyclerView 的繪製流程。接下來我們再看看它在滾動的時候程式碼又是怎麼呼叫的。
說到滾動,自然要看 onTouch()
方法的 MOVE 狀態。
case MotionEvent.ACTION_MOVE: {
final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
...
}
if (mScrollState != SCROLL_STATE_DRAGGING) {
...
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
} break;
看到這段程式碼的時候,特意去搜了一下,MotionEventCompat 這個類是幹嘛的。 他是 v4 包裡面提供的一個工具類,用於相容低版本的觸控式螢幕手勢。平時用的時候更多的是用它來處理多點觸控的情況,當成MotionEvent
就可以了。
dispatchNestedPreScroll()
用於處理巢狀邏輯,例如在 ScrollView 裡面放一個 RecyclerView ,如果是以前用 ListView ,還得要把高度寫死,禁止 ListView 的複用和滾動邏輯,而 RecyclerView 則完全不需要更多處理,直接用就是了。而且有一個非常好的地方,如果放到 ScrollView 裡面,ListView 的 ItemView 是不會複用的,而 RecyclerView
因為是全域性公用一套快取池,雖說巢狀到 ScrollView 效率會低很多,但比起 ListView 巢狀要好很多,之後講快取池的時候,我們繼續講。
再之後,如果在相應方向上手指move的距離達到最大值,則認為需要滾動,並設定為滾動狀態(SCROLL_STATE_DRAGGING),這個最大距離預設是 8 個畫素。
接著走出 if 塊,如果是滾動狀態,則呼叫滾動方法scrollByInternal()
執行相應方向的滾動。滾動的距離當然就是手指移動的距離。跟進去看,果然是呼叫了LinearLayoutManager.scrollBy()
方法,又印證了前面【更多操作】裡面講 LayoutManager 可以滾動 RecyclerView 的方法。
以上就是滾動的邏輯了。 但是沒完,就像 ListView,在手指劃過以後,手指離開了螢幕,相關性一樣,View 自己依舊可以自己滾動一段距離。
既然手指離開了螢幕,那就去 UP 或者 CANCEL 狀態去找。
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
} break;
case MotionEvent.ACTION_UP: {
mVelocityTracker.addMovement(vtev);
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
final float yvel = canScrollVertically ?
-VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0;
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetTouch();
} break;
ACTION_CANCEL 裡面只有一個 cancelTouch() ,那麼自然是在 UP 狀態裡面實現的慣性滾動。
看到了一個 mVelocityTracker 物件,大概原理也就清楚了,慣性滾動多長,肯定是跟手指移動的速度有關了。
再往下,跟進fling()
方法裡面看:呼叫了mViewFlinger.fling(velocityX, velocityY);
再進:
mScroller.fling(0, 0, velocityX, velocityY,
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
原來是呼叫了 Scroller 類的fling()
方法,再仔細看一下,發現是ScrollerCompat
看名字,估計又是用來相容舊版本的 support 包裡面的 Scroller 類。關於這個Scroller
類,他是一個可以用來實現平滑滾動效果的類,其實內部實現也是通過一點一點移動
view,利用了人眼的視覺暫留。
回收與複用
前面講 layout、滾動的時候,都出現了一個東西,叫 Recycler,現在我們就來看看他到底是個什麼。
public final class 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 RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
這麼多的集合,還有什麼Pool,ViewCache。看來他就是一個超大型的快取器了。
事實上他確實就是一個超大型的快取器,擁有三級快取(如果算上建立的那一次,應該是四級了),這麼大的快取系統,究竟是如何完成的?
- 第一級快取:
就是上面的一系列 mCachedViews。如果仍依賴於 RecyclerView (比如已經滑動出可視範圍,但還沒有被移除掉),但已經被標記移除的 ItemView 集合會被新增到 mAttachedScrap 中。然後如果 mAttachedScrap 中不再依賴時會被加入到 mCachedViews 中。 mChangedScrap 則是儲存 notifXXX 方法時需要改變的 ViewHolder 。
- 第二級快取:
ViewCacheExtension 是一個抽象靜態類,用於充當附加的快取池,當 RecyclerView 從第一級快取找不到需要的 View 時,將會從ViewCacheExtension 中找。不過這個快取是由開發者維護的,如果沒有設定它,則不會啟用。通常我們也不會去設定他,系統已經預先提供了兩級快取了,除非有特殊需求,比如要在呼叫系統的快取池之前,返回一個特定的檢視,才會用到他。
- 第三級快取:
最強大的快取器。之前講了,與 ListView 直接快取 ItemView 不同,從上面程式碼裡我們也能看到,RecyclerView 快取的是 ViewHolder。而 ViewHolder 裡面包含了一個 View 這也就是為什麼在寫 Adapter 的時候 必須繼承一個固定的 ViewHolder 的原因。首先來看一下 RecycledViewPool:
public static class RecycledViewPool {
// 根據 viewType 儲存的被廢棄的 ViewHolder 集合,以便下次使用
private SparseArray<ArrayList<ViewHolder>> mScrap = new SparseArray<ArrayList<ViewHolder>>();
/**
* 從快取池移除並返回一個 ViewHolder
*/
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;
}
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList scrapHeap = getScrapHeapForType(viewType);
if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
return;
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
/**
* 根據 viewType 獲取對應快取池
*/
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;
}
}
從名字來看,他是一個快取池,實現上,是通過一個預設為 5 大小的 ArrayList 實現的。這一點,同 ListView 的 RecyclerBin 這個類一樣。很奇怪為什麼不用 LinkedList 來做,按理說這種不需要索引讀取的快取池,用連結串列是最合適的。
然後每一個 ArrayList 又都是放在一個 Map 裡面的,SparseArray 這個類我們在講效能優化的時候已經多次提到了,就是兩個陣列,用來替代 Map 的。
把所有的 ArrayList 放在一個 Map 裡面,這也是 RecyclerView 最大的亮點,這樣根據 itemType 來取不同的快取 Holder,每一個 Holder 都有對應的快取,而只需要為這些不同 RecyclerView 設定同一個 Pool 就可以了。
這一點我們在 Pool 的 setter 方法上可以看到註釋:
/**
* Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
* This can be useful if you have multiple RecyclerViews with adapters that use the same
* view types, for example if you have several data sets with the same kinds of item views
* displayed by a {@link android.support.v4.view.ViewPager ViewPager}.
*
* @param pool Pool to set. If this parameter is null a new pool will be created and used.
*/
public void setRecycledViewPool(RecycledViewPool pool) {
mRecycler.setRecycledViewPool(pool);
}
在類似 ViewPager 這種檢視中,所有 RecyclerView 的 holder 是共存於同一個 Pool 中的。