RecyclerView源碼分析
來自: http://www.jcodecraeer.com//a/anzhuokaifa/androidkaifa/2016/0307/4032.html
概述
之前面試的時候經常有人問是否用過RecyclerView,最近項目中也大量用到RecyclerView。對于有點追求的碼工來時,當然不會滿足于僅僅會使用這一層次,學姐就是一個有追求的妹紙。我先從普通的AdapterView和RecyclerView的比較說起,后面再詳細介紹幾個關鍵類。
AdapterView vs. RecyclerView
-
Item復用方面:
RecyclerView內置了RecyclerViewPool、多級緩存、ViewHolder,而AdapterView需要 手動添加ViewHolder且復用功能也沒RecyclerView更加完善 -
樣式豐富方面:
RecyclerView通過支持水平、垂直和表格列表及其他更復雜形式,而AdapterView只支持具體某一種 -
效果增強方面:
RecyclerView內置了ItemDecoration和ItemAnimator,可以自定義繪制itemView之間的一些特殊UI或item項數據變化時的動畫效果,而用AdapterView實現時采取的做法是將這些特殊UI作為itemView的一部分,設置可見不可見決定是否展現,且數據變化時的動畫效果沒有提供,實現起來比較麻煩 -
代碼內聚方面:
RecyclerView將功能密切相關的類寫成內部類,如ViewHolder,Adapter,而AdapterView沒有
1. Recycler
(1)Recycler簡介
Recycler用于管理已經廢棄或與RecyclerView分離的(scrapped or detached)item view,便于重用。
Scrapped view指依附于RecyclerView,但被標記為可移除或可復用的view。
LayoutManager獲取Adapter某一項的View時會使用Recycler。當復用的View有效(clean)時,View能直接被復用,反之若View失效(dirty)時,需要重新綁定View。對于有效的View,如果不主動調用request layout,則不需要重新測量大小就能復用
(2)原理解析
在分析Recycler的復用原理之前,我們先了解下如下兩個類:
RecycledViewPool
RecyclerViewPool用于多個RecyclerView之間共享View。只需要創建一個RecyclerViewPool實例,然后調用RecyclerView的setRecycledViewPool(RecycledViewPool)方法即可。RecyclerView默認會創建一個RecyclerViewPool實例。
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<ViewHolder>();
mScrap.put(viewType, scrap);
if (mMaxScrap.indexOfKey(viewType) < 0) {
mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
}
}
return scrap;
}
} 通過源碼我們可以看出mScrap是一個<viewType, List
ViewCacheExtension
ViewCacheExtension是一個由開發者控制的可以作為View緩存的幫助類。調用Recycler.getViewForPosition(int)方法獲取View時,Recycler先檢查attached scrap和一級緩存,如果沒有則檢查ViewCacheExtension.getViewForPositionAndType(Recycler, int, int),如果沒有則檢查RecyclerViewPool。注意:Recycler不會在這個類中做緩存View的操作,是否緩存View完全由開發者控制。
public abstract static class ViewCacheExtension {
abstract public View getViewForPositionAndType(Recycler recycler, int position, int type);
} 現在大家熟悉了RecyclerViewPool和ViewCacheExtension的作用后,下面開始介紹Recycler。 如下是Recycler的幾個關鍵成員變量和方法:
private ArrayList<ViewHolder> mAttachedScrap
private ArrayList<ViewHolder> mChangedScrap與RecyclerView分離的ViewHolder列表。
private ArrayList<ViewHolder> mCachedViewsViewHolder緩存列表。
private ViewCacheExtension mViewCacheExtension開發者控制的ViewHolder緩存
private RecycledViewPool mRecyclerPool提供復用ViewHolder池。
public void bindViewToPosition(View view, int position)
將某個View綁定到Adapter的某個位置。
public View getViewForPosition(int position)
獲取某個位置需要展示的View,先檢查是否有可復用的View,沒有則創建新View并返回。具體過程為:
(1)檢查mChangedScrap,若匹配到則返回相應holder
(2)檢查mAttachedScrap,若匹配到且holder有效則返回相應holder
(3)檢查mViewCacheExtension,若匹配到則返回相應holder
(4)檢查mRecyclerPool,若匹配到則返回相應holder
(5)否則執行Adapter.createViewHolder(),新建holder實例
(6)返回holder.itemView
(7)注:以上每步匹配過程都可以匹配position或itemId(如果有stableId)
2. LayoutManager
LayoutManager主要作用是,測量和擺放RecyclerView中itemView,以及當itemView對用戶不可見時循環復用處理。 通過設置Layout Manager的屬性,可以實現水平滾動、垂直滾動、方塊表格等列表形式。其內部類Properties包含了所需要的大部分屬性
3. ViewHolder
對于傳統的AdapterView,需要在實現的Adapter類中手動加ViewHolder,RecyclerView直接將ViewHolder內置,并在原來基礎上功能上更強大。ViewHolder描述RecylerView中某個位置的itemView和元數據信息,屬于Adapter的一部分。其實現類通常用于保存findViewById的結果。 主要元素組成有:
public static abstract class ViewHolder {
View itemView;//itemView
int mPosition;//位置
int mOldPosition;//上一次的位置
long mItemId;//itemId
int mItemViewType;//itemViewType
int mPreLayoutPosition;
int mFlags;//ViewHolder的狀態標志
int mIsRecyclableCount;
Recycler mScrapContainer;//若非空,表明當前ViewHolder對應的itemView可以復用
} 關于ViewHolder,我最想介紹的是mFlags。
FLAG_BOUND――ViewHolder已經綁定到某個位置,mPosition、mItemId、mItemViewType都有效
FLAG_UPDATE――ViewHolder綁定的View對應的數據過時需要重新綁定,mPosition、mItemId還是一致的
FLAG_INVALID――ViewHolder綁定的View對應的數據無效,需要完全重新綁定不同的數據
FLAG_REMOVED――ViewHolder對應的數據已經從數據集移除
FLAG_NOT_RECYCLABLE――ViewHolder不能復用
FLAG_RETURNED_FROM_SCRAP――這個狀態的ViewHolder會加到scrap list被復用。
FLAG_CHANGED――ViewHolder內容發生變化,通常用于表明有ItemAnimator動畫
FLAG_IGNORE――ViewHolder完全由LayoutManager管理,不能復用
FLAG_TMP_DETACHED――ViewHolder從父RecyclerView臨時分離的標志,便于后續移除或添加回來
FLAG_ADAPTER_POSITION_UNKNOWN――ViewHolder不知道對應的Adapter的位置,直到綁定到一個新位置
FLAG_ADAPTER_FULLUPDATE――方法addChangePayload(null)調用時設置
4. Adapter
和AdapterView中用到的BaseAdapter、ListAdapter等作用類似,都是作為itemView和data之間的適配器,將data綁定到某一個itemView上。差別在于,RecyclerView將Adapter內置作為其內部類,我認為將功能密切相關的類以內部類的形式定義使得代碼內聚更好,更便于理解與閱讀。
5. ItemDecoration
當我們想在某些item上加一些特殊的UI時,往往都是在itemView中先布局好,然后通過設置可見性來決定哪些位置顯示不顯示。RecyclerView將itemView和裝飾UI分隔開來,裝飾UI即ItemDecoration,主要用于繪制item間的分割線、高亮或者margin等。其源碼如下:
public static abstract class ItemDecoration {
//itemView繪制之前繪制,通常這部分UI會被itemView蓋住
public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
}
//itemView繪制之后繪制,這部分UI蓋在itemView上面
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
onDrawOver(c, parent);
}
//設置itemView上下左右的間距
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
} 6. ItemAnimator
過去AdapterView的item項操作往往是沒有動畫的。現在RecyclerView的ItemAnimator使得item的動畫實現變得簡單而樣式豐富,我們可以自定義item項不同操作(如添加,刪除)的動畫效果。
7. 觸摸事件監聽
關于Item項的手勢監聽事件,如單擊和雙擊沒有像其他AdapterView一樣分別提供具體接口,但是RecyclerView提供OnItemTouchListener接口和SimpleOnItemTouchListener實現類,大家可以通過繼承去實現自己想要的單擊雙擊或其他事件監聽。
ps:關于RecyclerView的具體使用,我提供一個鏈接供大家參考RecyclerView技術棧。今天就先講這么多,以后有新的體會會繼續補充的,各位期待吧~
附錄:
原文出處:某學姐的技術博客。