文章目錄
- Adapter、ViewHolder
- child view
- LayoutManager
- Recycler
- Scrap
- Dirty
- Index
- Position
- layout position 和 adapter position
- 四級緩存
瀏覽本文前推薦先閱讀 Android入門(九)| 滾動控件 ListView 與 RecyclerView
Adapter、ViewHolder
Adapter: A subclass of RecyclerView.Adapter responsible for providing views that represent items in a data set.
-
翻譯:RecyclerView.Adapter 的子類。
Adapter
(適配器) 負責提供表示data set
(數據集) 中items
(子項) 的views
(視圖)。 -
解析:
RecyclerView
只是一個ViewGroup
,它只認識View
,不清楚構成 前端界面View 的 后端Data數據的具體結構。因此,RecyclerView
需要一個Adapter
將Data
轉換為RecyclerView
認識的ViewHolder
。 -
ViewHolder: 對
view
進行操作,在ViewHolder
中會將view
中的各個控件實例化,然后進行管理,如:設置控件的點擊事件等。
child view
RecyclerView滾動控件
中的 最小子元素,比如對于布局方式為 LinearLayout
(線性布局) 的 RecyclerView
來說,child view
(子視圖) 就是每一行。
我個人理解為 RecyclerView
是由 data set
的所有數據構建而成的,而每個 child view
都是由某個 data item
(數據子項) 構建而成的。
LayoutManager
雖然 Adapter
已經將 data set
轉換為了 views
,但是以怎樣的布局顯示這些 views
也是一個問題。因此 RecyclerView
委托 LayoutManager
負責 view
布局的顯示管理。有多種布局方式供選擇,如:線性布局、網格布局等。
PS:LayoutManager 只負責將 view 呈現在 Recycle 中,并不直接負責對 view 的管理,view 的管理由下面的 Recycler 負責。
Recycler
管理不在前臺的 View
,對 View
進行緩存,以便后續重用,避免每次都需要加載 view
,顯著提高性能。LayoutManager
在需要 View
的時候會向 Recycler
進行索取,當 LayoutManager
不需要 View
(試圖滑出)的時候,就直接將廢棄的 View
丟給 Recycler
。
Scrap
在加載布局期間已進入 臨時分離(temporarily detached) 狀態的子視圖。 Scrap views
可以在不與 parent RecyclerView
完全分離(fully detached) 的情況下重用。 重用時需要做進一步判定是否需要修改 scrap views
:
- 如果不需要 rebinding重新綁定 則不需要修改。
- 如果該
view
被視為dirty
,則由 適配器Adapter 進行修改。
Dirty
在顯示之前必須由 適配器 重新綁定rebound 的 子視圖child view。
Index
調用 ViewGroup.getChildAt()
時使用的參數,已經添加到 RecyclerView
中的 子view
的索引。 與 Position
形成對比,Position
是數據的位置,Index
是視圖的位置。
Position
Position: The position of a data item within an Adapter.
- 適配器中
data item
(數據子項) 的位置。
Position 從大的方向可以分為兩種情況:
- 方法
onBindViewHolder()
中的參數position
; - 通過
ViewHolder
的getLayoutPosition()/getAdapterPosition()
方法得到的layout position/adapter position
。
對于第一種 positon,我們通常使用它來得到子視圖,舉個例子:
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {// 通過 position 獲取 DataSet 數據集(如數組等)中對應的子項 DateDate date = DataSet.get(position);
}
對于第二種
layout position 和 adapter position
與 ListView 不同, RecyclerView 將 跟蹤 Adapter
的工作從 RecyclerView.LayoutManager 中抽離,交給 RecyclerView.Adapter 類。ListView 是沒有 “ListView.Adapter” 的,ListView 中需要用到適配器的時候,都是自定義一個 BaseAdapter類 的子類,而 RecyclerView 已經為開發者封裝好了 RecyclerView.Adapter ,如此一來 RecyclerView 便能夠在更新布局期間對 data set
(數據集)進行批處理(等待數據修改完成再傳遞給布局,此等待時間小于 16 毫秒)。這可以將 LayoutManager
從跟蹤 Adapter
的工作中解脫出來,而去負責 calculate animations
(更新界面)的工作。這有助于提高性能,因為所有 view bindings
(視圖綁定) 都同時發生,并且避免了不必要的綁定。
不過這種抽象方式導致了在 RecyclerView
中有兩種與 位置 相關的方法:
- layout position: 在最近一次布局更新后
view item
在布局中的位置,這個位置是站在LayoutManager
的角度得到的view
的位置,也是布局更新后用戶直觀看到的布局。通過getLayoutPosition()
得到。 - adapter position:
ViewHolder item
在適配器中的位置,這是站在Adapter
的角度得到的ViewHolder
所在的位置,通常是用戶單擊某個ViewHolder item
時,詢問Adapter
得到的。通過getAdapterPosition()
得到。
當適配器內容改變時,并且調用 adapter.notify*
方法 從 RecyclerView
請求一個新的布局。從那一刻起,新布局更新完成(此時間小于 16 毫秒),兩個 position
可能不匹配,因為布局還沒有反映適配器的變化。除此之外,這兩個 position
在大多數時候是相等的。
getAdapterPosition()
使用時的注意事項:
-
由于調用
notifyDataSetChanged()
會使所有內容無效,因此RecyclerView
在更新下一個布局之前不知道ViewHolder
的adapter position
。在這種情況下,getAdapterPosition()
將返回RecyclerView#NO_POSITION( -1)
。 -
但是假設調用了
notifyItemInserted(0)
,先前adapter position = 0
的ViewHolder
調用getAdapterPosition()
將立即返回adapter position = 1
。因此,只要是對granular
(最小粒度,指單元子項)調用notify events
(應該指的是notifyItem*
方法),那么即使布局尚未更新完成,也能立刻獲得adapter position
。 -
如果用戶點擊時
getAdapterPosition()
返回NO_POSITION
,那么最好忽略那個點擊,因為不知道用戶點擊了什么(除非有一些其他的機制能夠確認被點擊的是什么,例如用于查找單元子項的穩定ID)。
四級緩存
緩存級別 | 詳細描述 |
---|---|
一級緩存 mAttachedScrap/mChangedScrap | 緩存屏幕可見范圍的 ViewHolder |
二級緩存 mCachedViews | 按 child View 的 position 或 id 緩存滑動時即將與 RecyclerView 分離的 ViewHolder。 |
三級緩存 mViewCacheExtension | 開發者自行實現的緩存。 |
四級緩存 mRecyclerPool | ViewHolder緩存池,本質上是一個 android.util.SparseArray,其中 key 是 ViewType(int類型),value 存放的是 ArrayList< ViewHolder> ,默認每個 ArrayList 中最多存放5個 ViewHolder。 |