一、概述
對于RecyclerView
的學(xué)習(xí),主要是需要掌握以下幾點:
- 數(shù)據(jù):
Adapter
- 使用:RecyclerView - Adapter
- 進階:BaseRecyclerViewAdapterHelper
- 布局:
LayoutManager
- 使用:RecyclerView - LayoutManager
- 進階:自定義
- 動畫:
ItemAnimator
- 使用
- 進階:RecyclerViewItemAnimators
- 裝飾:
ItemDecorator
- 使用:RecyclerView - ItemDecoration
- 手勢:
ItemTouchHelper
- 使用:RecyclerView - ItemTouchHelper
要理解整個RecyclerView
的思想,有一個視頻是一定要看的:RecyclerView ins and outs - Google I/O 2016。今天,我們就通過這個視頻,把上面所學(xué)到的東西串聯(lián)起來。
二、為什么要使用RecyclerView
RecyclerView
誕生的目的就是為了替代ListView
,我們先總結(jié)一下在使用ListView
過程當(dāng)中所遇到的問題:
- 復(fù)用
Item
需要編寫很多的代碼
在使用ListView
的時候,有經(jīng)驗的程序員一定會告訴你在getView
中要這么寫,如果忘了,那么會產(chǎn)生很嚴(yán)重的性能問題。
if (convertView == null) {
//通過LayoutInflator生成convertView,并產(chǎn)生一個ViewHolder,通過setTag關(guān)聯(lián)起來.
} else {
//通過getTag獲取ViewHolder,進行更新操作.
}
- 焦點沖突問題
當(dāng)Item
有焦點時,Item
的子控件就無法獲取到焦點;而如果子控件搶奪了焦點,那么Item
的點擊事件又不能響應(yīng),這個相信大家都遇到過。 - 重復(fù)的
API
ListView
中提供了很多的API
,但是這些API
又和View
的一些API
重復(fù)了,例如我們可以給ListView
設(shè)置setOnItemClickListener
,也可以在getView
中給某個View
設(shè)置setOnClickListener
,這就讓人很疑惑,到底應(yīng)當(dāng)選用哪個。 - 動畫
當(dāng)我們需要在ListView
中進行添加、刪除、移動等操作的時候,如果希望加上動畫,那么是很困難的,根本原因是我們是通過Adapter
通知ListView
進行更新,然而ListView
根本就沒法確定到底是哪些View
發(fā)生了變化。 - 更加復(fù)雜的布局需求
ListView
在布局是規(guī)整的列表的時候能滿足大多數(shù)人的使用,然而如果想要實現(xiàn)像瀑布流這種復(fù)雜的布局,并且保證View
能夠復(fù)用,那么需要編寫很多的代碼。
如果之前有了解過RecyclerView
的基本用法,那么你會發(fā)現(xiàn),對于上述這些問題,它都給出了自己的解決方案:
- 強制使用開發(fā)者使用
ViewHolder
,提供了onCreateViewHolder
和onBindViewHolder
這兩個方法,把創(chuàng)建View
和綁定View
的操作分離開。 - 把焦點交給系統(tǒng)處理。
- 去掉了
onItemClickListener
,以及一些重復(fù)的API
。 - 在
Adapter
中增加了notifyItemChanged()
等方法,讓我們可以指定變化的類型和范圍,并且提供了setItemAnimator()
方法,讓開發(fā)者能夠方便地定義添加、刪除、移動的動畫。 - 把布局的工作抽象出來,放到了
LayoutManager
當(dāng)中,并預(yù)制了瀑布流布局。
了解了這些,我們就能知道RecyclerView
能幫我們解決什么問題,也就能更好地理解它為什么要這么設(shè)計,下面就開始進入真正的RecyclerView
的學(xué)習(xí)。
三、RecyclerView
架構(gòu)
整個
RecyclerView
體系包含三大組件:
-
LayoutManager
:position the view
-
ItemAnimator
:animate the view
-
Adapter
:provide the view
這三大組件各司其職,而RecyclerView
負責(zé)管理,就組成了整個RecyclerView
的架構(gòu)。
3.1 LayoutManager
LayoutManager
需要負責(zé)以下幾部分的工作:
-
Position
它負責(zé)View
的擺放,可以是線性、宮格、瀑布流式或者任意類型,而RecyclerView
不知道也不關(guān)心這些,這是LayoutManager
的職責(zé)。 -
Scroll
對于滾動事件的處理,RecyclerView
負責(zé)接收事件,但是最終還是由LayoutManager
進行處理滾動后的邏輯,因為只有它在知道View
具體擺放的位置。 -
Focus traversal
當(dāng)焦點轉(zhuǎn)移導(dǎo)致需要一個新的Item
出現(xiàn)在可視區(qū)域中時,也是由LayoutManager
處理的。
3.2 Adapter
Adapter
需要負責(zé)以下幾部分的工作:
- 創(chuàng)建
View
和ViewHolder
,后者作為整個復(fù)用機制的跟蹤單元。 - 把具體位置的
Item
和ViewHolder
進行綁定,并存儲相關(guān)的信息。 - 通知
RecyclerView
數(shù)據(jù)變化,支持局部的更新,在提高效率的同時也有效地支持了動畫。 -
Item
點擊事件的處理。 - 多類型布局的支持。
四、ViewHolder
的生命周期
4.1 LayoutManager
請求RecyclerView
提供指定position
的View
ViewHolder
是和View
相綁定的,同時它也是整個復(fù)用框架的跟蹤單元。在RecyclerView
體系中,對ViewHolder
采用了二級緩存,分為Cache
和Recycled Pool
,當(dāng)LayoutManager
向RecyclerView
請求位于某個Position
的View
時,Recycled View
會先去Cache
中尋找,如果找到,那么直接返回;如果找不到,那么再去Recycled Pool
中尋找,下面就是整個尋找過程的幾種情況:
- 命中
Cache
這種情況下,不會調(diào)用Adapter
的onCreateViewHolder
或者onBindViewHolder
方法:
-
Cache
不存在,Recycled Pool
也不存在
這種情況下,會調(diào)用Adapter
的onCreateViewHolder
方法,讓它提供一個對應(yīng)viewType
的ViewHolder
,我們在其中建立ViewHolder
和View
之間的關(guān)聯(lián)。
-
Cache
不存在,Recycled Pool
存在
這種情況下,會回調(diào)Adapter
的onBindViewHolder
方法,我們在其中使用當(dāng)前的數(shù)據(jù)集合來更新ViewHolder
所綁定的itemView
的狀態(tài)。
4.2 LayoutManager
找到對應(yīng)位置的View
LayoutManager
通過addView
方法把之前找到的View
添加進RecyclerView
,RecyclerView
通過onViewAttachToWindow(VH viewHolder)
方法,通知Adapter
這個viewHolder
所關(guān)聯(lián)的itemView
已經(jīng)被添加到了布局當(dāng)中,
4.3 LayoutManager
請求RecyclerView
移除某一個位置的View
4.3.1 普通情況
當(dāng)LayoutManager
發(fā)現(xiàn)不再需要某一個position
的View
時,它會通知RecyclerView
,RecyclerView
通過onViewDetachFromWindow(VH viewHolder)
通知Adapter
和它綁定的itemView
被移出了。同時,RecyclerView
判斷它是否能夠被緩存,假設(shè)能夠被緩存,那么它會先被放到Cache
當(dāng)中,在Cache
中又會判斷它內(nèi)部是否有需要轉(zhuǎn)移到Recycled Pool
中的ViewHolder
,在放入之后回收池后,通過onViewRecycled(VH viewHolder)
方法通知Adapter
它被回收了。
4.3.2 特殊情況
在上面的普通的情況中,onViewDetachFromWindow(VH viewHolder)
是立即被回調(diào)的。然而在實際當(dāng)中,由于我們需要對View
的添加、刪除做一些過度動畫,這時候,我們需要等待ItemAnimator
進行完動畫操作之后,才做detach
和recycle
的邏輯,這一過程對于LayoutManager
是不可見的。
4.4 ViewHolder
的銷毀
在一般情況下,我們不會去銷毀ViewHolder
,而是把它放入到緩存當(dāng)中,除非出現(xiàn)以下兩種情況。
4.4.1 ViewHolder
所綁定的itemView
當(dāng)前狀態(tài)異常
在放入Recycled Pool
時,會去檢查itemView
的狀態(tài)是否正常。這一操作的目的主要是為了避免出現(xiàn)諸如此類的情況:當(dāng)前itemView
正在執(zhí)行動畫,此時它可能呈現(xiàn)半透明的狀態(tài),如果此時把它放入到回收池中,那么當(dāng)另一個位置的position
需要復(fù)用它時就可能會出現(xiàn)問題。
當(dāng)出現(xiàn)上面的情況后,Recycled Pool
會先通過Adapter
的onFailedToRecycled(VH viewHolder)
告訴它我們現(xiàn)在出現(xiàn)了異常的情況,由Adapter
的實現(xiàn)者通過返回值來決定是否仍然要把它放入到Recycled Pool
,默認(rèn)是返回false
,也就是不放入,那么這個ViewHolder
就會被銷毀了。
4.4.2 Recycled Pool
中已經(jīng)沒有足夠的空間
Recycled Pool
的空間并不是無限大的,因此,如果沒有足夠的空間存放要被回收的ViewHolder
,那么它也會被銷毀。
造成這種情況的一般是動畫引起的,例如,我們調(diào)用了
notifyItemRangeChanged(0, getItemCount())
方法,這時候為了進行漸出漸進的動畫,那么我們就需要創(chuàng)建兩倍的ViewHolder
,出現(xiàn)這種情況時一般有兩種解決方法:
- 只通知具體發(fā)生變化的
Item
- 通過
pool.setMaxRecycledViews(type, count)
改變回收池的大小。
五、ItemAnimator
對于Item
的動畫,主要有以下幾種情況:
- 添加:
Fade In
- 刪除:
Fade Out
- 移動:
Translate
- 更新:
Cross Fade
RecyclerView
對于動畫的處理采用了Predictive
的方式,除了當(dāng)前已經(jīng)在RecyclerView
布局中的View
(實線框部分),它還需要知道在屏幕意外的信息(虛線框部分),這樣在H
被刪除的時候,它才能夠?qū)?code>J-K進行上移動畫,并把原來不在屏幕內(nèi)的L
上移到可視范圍之內(nèi)。
六、ChildHelper
和AdapterHelper
6.1 ChildHelper
對于ChildHelper
的作用是:Provide a virtual children list to layoutmanager
,下面我們就首先看一下為什么需要它。
6.1.1 解決什么問題
我們看下面這種情況,假如LayoutManager
想要移除一個View
,而ItemAnimator
又希望給這一移除的操作增加一個動畫,那么這時候就會產(chǎn)生沖突,到底應(yīng)該怎么辦,為此,RecyclerView
通過ChildHelper
來把它們隔離開。
6.1.2 解決問題的方法
當(dāng)RecyclerView
收到LayoutManager
要求改變布局的請求時,它并不是直接去更改ViewGroup
,而是讓ChildHelper
和ItemAnimator
去協(xié)調(diào),并由它來操作ViewGroup
。
最明顯的例子是,假如我們當(dāng)前列表中狀態(tài)為
0,1,2,3
,此時我們移除了position=0
的Item
,這時候假如刪除的動畫還沒有完成,那么LayoutManager
和RecyclerView
的getChildAt(0)
返回值將會不同,因為在LayoutManager
并不清楚ChildHelper
的存在,在它看來,position=0
的Item
已經(jīng)被移除了。
layoutManager.getChildAt(0); //return 1;
recyclerView.getChildAt(0); //return 0;
6.2 AdapterHelper
而AdapterHelper
所解決的問題和ChildHelper
類似,ChildHelper
是處理View
的,而AdapterHelper
用來跟蹤ViewHolder
的,其作用為:
Tracks ViewHolder positions
Virtual Adapter for LayoutManager
說起來可能比較抽象,我們用下面這種圖理解一下,當(dāng)我們移動某個Item
并且它的onLayout
方法還沒有完成,那么Adapter
和Layout
的postion
是不相同的:
七、ItemDecoration
ItemDecoration
用來在RecyclerView
的Canvas
上進行額外的繪制操作,我們不僅可以在單個Item
(例如給每個Item
添加分割線)的Canvas
上進行繪制,也可以在整個RecyclerView
的Canvas
上進行繪制,此外,我們還可以指定Item
之間的間隔:
Custom Drawing on RecyclerViews Canvas
Add offset to View bounds
Have multiple ItemDecoration
需要注意的點:
Do not try to access to adapter
Keep necessary information in viewHolder
General onDraw rules apply
recyclerView.getChildViewHolder(View view)
參考文章:Android RecyclerView 使用完全解析 體驗藝術(shù)般的控件。
八、RecycledViewPool
RecyclerViewPool
用來緩存那些回收的View
,這些緩存不僅可以提供給單個RecyclerView
使用,還可以提供和別的自定義控件共享。
Sanctuary for reserve ViewHolders
Can be shared between RecyclerViews or Custom ViewGroups
PerActivity Context
九、ItemTouchHelper
之前使用ListView
的時候,如果需要支持側(cè)滑刪除、拖動排序這種操作,那么我們一般用引入一些開源庫,現(xiàn)在RecyclerView
已經(jīng)幫我們提供了實現(xiàn)的接口,通過重寫ItemTouchHelper
的方法,就可以實現(xiàn)上面提到的那些操作。
Drag & Drop
Swipe to dismiss
參考文章:RecyclerView 進階:使用 ItemTouchHelper 實現(xiàn)拖拽和側(cè)滑刪除
十、Tips
-
onBind Position != final
,use holder.getAdapterPostion()
如果我們像下面這樣,在onBindViewHolder
中綁定了監(jiān)聽:
public void onBindViewHolder(final ViewHolder, final int position) {
holder.itemView.setOnClickListener(new View.onClickListener) {
@Override
public void onClick(View view) {
removeAtPostion(position);
}
}
}
由于Item
會被添加、刪除、移動,因此,我們在onBindViewHolder
中獲得位置,并不一定是當(dāng)前的位置,例如像下面這樣:
onBindViewHolder(holder, 5);
notifyItemMoved(5, 15);
holder.itemView.callOnClick();
那么就會得到錯誤的位置,這時候應(yīng)當(dāng)使用holder.getAdapterPostion()
來保證能夠得到預(yù)期的結(jié)果。
-
Payloads
通過onBindViewHolder
中的List payloads
,我們可以指定在bind
的時候只更新某一部分的信息,而不是全部更新。 -
onCreate means create
在onCreateViewHolder
中,始終應(yīng)當(dāng)返回一個新的ViewHolder
,而不是返回一個緩存的ViewHolder
。 -
Adapter position and Layout position
就像我們前面在AdapterHelper
中討論的那樣,在某些時刻,Adapter Position
和Layout Position
并不相等,我們應(yīng)當(dāng)根據(jù)情況選擇需要使用哪個,Adapter Position
數(shù)據(jù)所處的位置,而Layout Position
則對應(yīng)當(dāng)前View
的所處的位置。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.lxweimin.com/p/fd82d18994ce
- 個人主頁:http://lizejun.cn
- 個人知識總結(jié)目錄:http://lizejun.cn/categories/