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> mCachedViews
ViewHolder緩存列表。
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技術棧。今天就先講這么多,以后有新的體會會繼續補充的,各位期待吧~
附錄:
原文出處:某學姐的技術博客。