RecyclerView 知識梳理(1) 綜述

澤毛發表於2017-12-21

一、概述

對於RecyclerView的學習,主要是需要掌握以下幾點:

要理解整個RecyclerView的思想,有一個視訊是一定要看的:RecyclerView ins and outs - Google I/O 2016。今天,我們就通過這個視訊,把上面所學到的東西串聯起來。

二、為什麼要使用RecyclerView

RecyclerView誕生的目的就是為了替代ListView,我們先總結一下在使用ListView過程當中所遇到的問題:

  • 複用Item需要編寫很多的程式碼 在使用ListView的時候,有經驗的程式設計師一定會告訴你在getView中要這麼寫,如果忘了,那麼會產生很嚴重的效能問題。
if (convertView == null) {
     //通過LayoutInflator生成convertView,併產生一個ViewHolder,通過setTag關聯起來.       
} else {
    //通過getTag獲取ViewHolder,進行更新操作.
}
複製程式碼
  • 焦點衝突問題 當Item有焦點時,Item的子控制元件就無法獲取到焦點;而如果子控制元件搶奪了焦點,那麼Item的點選事件又不能響應,這個相信大家都遇到過。
  • 重複的API ListView中提供了很多的API,但是這些API又和View的一些API重複了,例如我們可以給ListView設定setOnItemClickListener,也可以在getView中給某個View設定setOnClickListener,這就讓人很疑惑,到底應當選用哪個。
  • 動畫 當我們需要在ListView中進行新增、刪除、移動等操作的時候,如果希望加上動畫,那麼是很困難的,根本原因是我們是通過Adapter通知ListView進行更新,然而ListView根本就沒法確定到底是哪些View發生了變化。
  • 更加複雜的佈局需求 ListView在佈局是規整的列表的時候能滿足大多數人的使用,然而如果想要實現像瀑布流這種複雜的佈局,並且保證View能夠複用,那麼需要編寫很多的程式碼。

如果之前有了解過RecyclerView的基本用法,那麼你會發現,對於上述這些問題,它都給出了自己的解決方案:

  • 強制使用開發者使用ViewHolder,提供了onCreateViewHolderonBindViewHolder這兩個方法,把建立View和繫結View的操作分離開。
  • 把焦點交給系統處理。
  • 去掉了onItemClickListener,以及一些重複的API
  • Adapter中增加了notifyItemChanged()等方法,讓我們可以指定變化的型別和範圍,並且提供了setItemAnimator()方法,讓開發者能夠方便地定義新增、刪除、移動的動畫。
  • 把佈局的工作抽象出來,放到了LayoutManager當中,並預製了瀑布流佈局。

瞭解了這些,我們就能知道RecyclerView能幫我們解決什麼問題,也就能更好地理解它為什麼要這麼設計,下面就開始進入真正的RecyclerView的學習。

三、RecyclerView架構

RecyclerView 知識梳理(1)   綜述
整個RecyclerView體系包含三大元件:

  • LayoutManagerposition the view
  • ItemAnimatoranimate the view
  • Adapterprovide the view

這三大元件各司其職,而RecyclerView負責管理,就組成了整個RecyclerView的架構。

3.1 LayoutManager

LayoutManager需要負責以下幾部分的工作:

  • Position 它負責View的擺放,可以是線性、宮格、瀑布流式或者任意型別,而RecyclerView不知道也不關心這些,這是LayoutManager的職責。
  • Scroll 對於滾動事件的處理,RecyclerView負責接收事件,但是最終還是由LayoutManager進行處理滾動後的邏輯,因為只有它在知道View具體擺放的位置。
  • Focus traversal 當焦點轉移導致需要一個新的Item出現在可視區域中時,也是由LayoutManager處理的。

3.2 Adapter

Adapter需要負責以下幾部分的工作:

  • 建立ViewViewHolder,後者作為整個複用機制的跟蹤單元。
  • 把具體位置的ItemViewHolder進行繫結,並儲存相關的資訊。
  • 通知RecyclerView資料變化,支援區域性的更新,在提高效率的同時也有效地支援了動畫。
  • Item點選事件的處理。
  • 多型別佈局的支援。

四、ViewHolder的生命週期

4.1 LayoutManager請求RecyclerView提供指定positionView

ViewHolder是和View相繫結的,同時它也是整個複用框架的跟蹤單元。在RecyclerView體系中,對ViewHolder採用了二級快取,分為CacheRecycled Pool,當LayoutManagerRecyclerView請求位於某個PositionView時,Recycled View會先去Cache中尋找,如果找到,那麼直接返回;如果找不到,那麼再去Recycled Pool中尋找,下面就是整個尋找過程的幾種情況:

  • 命中Cache 這種情況下,不會呼叫AdapteronCreateViewHolder或者onBindViewHolder方法:
    RecyclerView 知識梳理(1)   綜述
  • Cache不存在,Recycled Pool也不存在 這種情況下,會呼叫AdapteronCreateViewHolder方法,讓它提供一個對應viewTypeViewHolder,我們在其中建立ViewHolderView之間的關聯。
    RecyclerView 知識梳理(1)   綜述
  • Cache不存在,Recycled Pool存在 這種情況下,會回撥AdapteronBindViewHolder方法,我們在其中使用當前的資料集合來更新ViewHolder所繫結的itemView的狀態。
    RecyclerView 知識梳理(1)   綜述

4.2 LayoutManager找到對應位置的View

LayoutManager通過addView方法把之前找到的View新增進RecyclerViewRecyclerView通過onViewAttachToWindow(VH viewHolder)方法,通知Adapter這個viewHolder所關聯的itemView已經被新增到了佈局當中,

RecyclerView 知識梳理(1)   綜述

4.3 LayoutManager請求RecyclerView移除某一個位置的View

4.3.1 普通情況

LayoutManager發現不再需要某一個positionView時,它會通知RecyclerViewRecyclerView通過onViewDetachFromWindow(VH viewHolder)通知Adapter和它繫結的itemView被移出了。同時,RecyclerView判斷它是否能夠被快取,假設能夠被快取,那麼它會先被放到Cache當中,在Cache中又會判斷它內部是否有需要轉移到Recycled Pool中的ViewHolder,在放入之後回收池後,通過onViewRecycled(VH viewHolder)方法通知Adapter它被回收了。

RecyclerView 知識梳理(1)   綜述

4.3.2 特殊情況

在上面的普通的情況中,onViewDetachFromWindow(VH viewHolder)是立即被回撥的。然而在實際當中,由於我們需要對View的新增、刪除做一些過度動畫,這時候,我們需要等待ItemAnimator進行完動畫操作之後,才做detachrecycle的邏輯,這一過程對於LayoutManager是不可見的。

RecyclerView 知識梳理(1)   綜述

4.4 ViewHolder的銷燬

在一般情況下,我們不會去銷燬ViewHolder,而是把它放入到快取當中,除非出現以下兩種情況。

4.4.1 ViewHolder所繫結的itemView當前狀態異常

在放入Recycled Pool時,會去檢查itemView的狀態是否正常。這一操作的目的主要是為了避免出現諸如此類的情況:當前itemView正在執行動畫,此時它可能呈現半透明的狀態,如果此時把它放入到回收池中,那麼當另一個位置的position需要複用它時就可能會出現問題。 當出現上面的情況後,Recycled Pool會先通過AdapteronFailedToRecycled(VH viewHolder)告訴它我們現在出現了異常的情況,由Adapter的實現者通過返回值來決定是否仍然要把它放入到Recycled Pool,預設是返回false,也就是不放入,那麼這個ViewHolder就會被銷燬了。

RecyclerView 知識梳理(1)   綜述

4.4.2 Recycled Pool中已經沒有足夠的空間

Recycled Pool的空間並不是無限大的,因此,如果沒有足夠的空間存放要被回收的ViewHolder,那麼它也會被銷燬。

RecyclerView 知識梳理(1)   綜述
造成這種情況的一般是動畫引起的,例如,我們呼叫了notifyItemRangeChanged(0, getItemCount())方法,這時候為了進行漸出漸進的動畫,那麼我們就需要建立兩倍的ViewHolder,出現這種情況時一般有兩種解決方法:

  • 只通知具體發生變化的Item
  • 通過pool.setMaxRecycledViews(type, count)改變回收池的大小。

五、ItemAnimator

對於Item的動畫,主要有以下幾種情況:

  • 新增:Fade In
  • 刪除:Fade Out
  • 移動:Translate
  • 更新:Cross Fade

RecyclerView對於動畫的處理採用了Predictive的方式,除了當前已經在RecyclerView佈局中的View(實線框部分),它還需要知道在螢幕意外的資訊(虛線框部分),這樣在H被刪除的時候,它才能夠對J-K進行上移動畫,並把原來不在螢幕內的L上移到可視範圍之內。

RecyclerView 知識梳理(1)   綜述

六、ChildHelperAdapterHelper

6.1 ChildHelper

對於ChildHelper的作用是:Provide a virtual children list to layoutmanager,下面我們就首先看一下為什麼需要它。

6.1.1 解決什麼問題

我們看下面這種情況,假如LayoutManager想要移除一個View,而ItemAnimator又希望給這一移除的操作增加一個動畫,那麼這時候就會產生衝突,到底應該怎麼辦,為此,RecyclerView通過ChildHelper來把它們隔離開。

RecyclerView 知識梳理(1)   綜述

6.1.2 解決問題的方法

RecyclerView收到LayoutManager要求改變佈局的請求時,它並不是直接去更改ViewGroup,而是讓ChildHelperItemAnimator去協調,並由它來操作ViewGroup

RecyclerView 知識梳理(1)   綜述
最明顯的例子是,假如我們當前列表中狀態為0,1,2,3,此時我們移除了position=0Item,這時候假如刪除的動畫還沒有完成,那麼LayoutManagerRecyclerViewgetChildAt(0)返回值將會不同,因為在LayoutManager並不清楚ChildHelper的存在,在它看來,position=0Item已經被移除了。

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

說起來可能比較抽象,我們用下面這種圖理解一下,當我們移動某個Item並且它的onLayout方法還沒有完成,那麼AdapterLayoutpostion是不相同的:

RecyclerView 知識梳理(1)   綜述

七、ItemDecoration

ItemDecoration用來在RecyclerViewCanvas上進行額外的繪製操作,我們不僅可以在單個Item(例如給每個Item新增分割線)的Canvas上進行繪製,也可以在整個RecyclerViewCanvas上進行繪製,此外,我們還可以指定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 使用完全解析 體驗藝術般的控制元件

八、RecycledViewPool

RecyclerViewPool用來快取那些回收的View,這些快取不僅可以提供給單個RecyclerView使用,還可以提供和別的自定義控制元件共享。

  • Sanctuary for reserve ViewHolders
  • Can be shared between RecyclerViews or Custom ViewGroups
  • PerActivity Context

九、ItemTouchHelper

之前使用ListView的時候,如果需要支援側滑刪除、拖動排序這種操作,那麼我們一般用引入一些開源庫,現在RecyclerView已經幫我們提供了實現的介面,通過重寫ItemTouchHelper的方法,就可以實現上面提到的那些操作。

  • Drag & Drop
  • Swipe to dismiss

參考文章:RecyclerView 進階:使用 ItemTouchHelper 實現拖拽和側滑刪除

十、Tips

  • onBind Position != finaluse holder.getAdapterPostion() 如果我們像下面這樣,在onBindViewHolder中繫結了監聽:
public void onBindViewHolder(final ViewHolder, final int position) {
    holder.itemView.setOnClickListener(new View.onClickListener) {
        @Override
        public void onClick(View view) {
            removeAtPostion(position);
        }
    }
}
複製程式碼

由於Item會被新增、刪除、移動,因此,我們在onBindViewHolder中獲得位置,並不一定是當前的位置,例如像下面這樣:

onBindViewHolder(holder, 5);
notifyItemMoved(5, 15);
holder.itemView.callOnClick();
複製程式碼

那麼就會得到錯誤的位置,這時候應當使用holder.getAdapterPostion()來保證能夠得到預期的結果。

  • Payloads 通過onBindViewHolder中的List payloads,我們可以指定在bind的時候只更新某一部分的資訊,而不是全部更新。
  • onCreate means createonCreateViewHolder中,始終應當返回一個新的ViewHolder,而不是返回一個快取的ViewHolder
  • Adapter position and Layout position 就像我們前面在AdapterHelper中討論的那樣,在某些時刻,Adapter PositionLayout Position並不相等,我們應當根據情況選擇需要使用哪個,Adapter Position資料所處的位置,而Layout Position則對應當前**View的所處的位置**。

更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章