RecyclerView 知識梳理(1) 綜述

1、概述

對於RecyclerView的學習,主要是須要掌握如下幾點:html

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

2、爲何要使用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的基本用法,那麼你會發現,對於上述這些問題,它都給出了本身的解決方案:github

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

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

3、RecyclerView架構

整個 RecyclerView體系包含三大組件:

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

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

3.1 LayoutManager

LayoutManager須要負責如下幾部分的工做:架構

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

3.2 Adapter

Adapter須要負責如下幾部分的工做:app

  • 建立ViewViewHolder,後者做爲整個複用機制的跟蹤單元。
  • 把具體位置的ItemViewHolder進行綁定,並存儲相關的信息。
  • 通知RecyclerView數據變化,支持局部的更新,在提升效率的同時也有效地支持了動畫。
  • Item點擊事件的處理。
  • 多類型佈局的支持。

4、ViewHolder的生命週期

4.1 LayoutManager請求RecyclerView提供指定positionView

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

  • 命中Cache 這種狀況下,不會調用AdapteronCreateViewHolder或者onBindViewHolder方法:
  • Cache不存在,Recycled Pool也不存在 這種狀況下,會調用AdapteronCreateViewHolder方法,讓它提供一個對應viewTypeViewHolder,咱們在其中創建ViewHolderView之間的關聯。
  • Cache不存在,Recycled Pool存在 這種狀況下,會回調AdapteronBindViewHolder方法,咱們在其中使用當前的數據集合來更新ViewHolder所綁定的itemView的狀態。

4.2 LayoutManager找到對應位置的View

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

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它被回收了。

4.3.2 特殊狀況

在上面的普通的狀況中,onViewDetachFromWindow(VH viewHolder)是當即被回調的。然而在實際當中,因爲咱們須要對View的添加、刪除作一些過分動畫,這時候,咱們須要等待ItemAnimator進行完動畫操做以後,才作detachrecycle的邏輯,這一過程對於LayoutManager是不可見的。

4.4 ViewHolder的銷燬

在通常狀況下,咱們不會去銷燬ViewHolder,而是把它放入到緩存當中,除非出現如下兩種狀況。

4.4.1 ViewHolder所綁定的itemView當前狀態異常

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

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

Recycled Pool的空間並非無限大的,所以,若是沒有足夠的空間存放要被回收的ViewHolder,那麼它也會被銷燬。

形成這種狀況的通常是動畫引發的,例如,咱們調用了 notifyItemRangeChanged(0, getItemCount())方法,這時候爲了進行漸出漸進的動畫,那麼咱們就須要建立兩倍的 ViewHolder,出現這種狀況時通常有兩種解決方法:

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

5、ItemAnimator

對於Item的動畫,主要有如下幾種狀況:

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

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

6、ChildHelperAdapterHelper

6.1 ChildHelper

對於ChildHelper的做用是:Provide a virtual children list to layoutmanager,下面咱們就首先看一下爲何須要它。

6.1.1 解決什麼問題

咱們看下面這種狀況,假如LayoutManager想要移除一個View,而ItemAnimator又但願給這一移除的操做增長一個動畫,那麼這時候就會產生衝突,到底應該怎麼辦,爲此,RecyclerView經過ChildHelper來把它們隔離開。

6.1.2 解決問題的方法

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

最明顯的例子是,假如咱們當前列表中狀態爲 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是不相同的:

7、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 使用徹底解析 體驗藝術般的控件

8、RecycledViewPool

RecyclerViewPool用來緩存那些回收的View,這些緩存不只能夠提供給單個RecyclerView使用,還能夠提供和別的自定義控件共享。

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

9、ItemTouchHelper

以前使用ListView的時候,若是須要支持側滑刪除、拖動排序這種操做,那麼咱們通常用引入一些開源庫,如今RecyclerView已經幫咱們提供了實現的接口,經過重寫ItemTouchHelper的方法,就能夠實現上面提到的那些操做。

  • Drag & Drop
  • Swipe to dismiss

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

10、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 知識梳理系列:

相關文章
相關標籤/搜索