RecyclerView 知識梳理(1) - 綜述

一、概述

對于RecyclerView的學(xué)習(xí),主要是需要掌握以下幾點:

要理解整個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,提供了onCreateViewHolderonBindViewHolder這兩個方法,把創(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體系包含三大組件:

  • LayoutManagerposition the view
  • ItemAnimatoranimate the view
  • Adapterprovide 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)建ViewViewHolder,后者作為整個復(fù)用機制的跟蹤單元。
  • 把具體位置的ItemViewHolder進行綁定,并存儲相關(guān)的信息。
  • 通知RecyclerView數(shù)據(jù)變化,支持局部的更新,在提高效率的同時也有效地支持了動畫。
  • Item點擊事件的處理。
  • 多類型布局的支持。

四、ViewHolder的生命周期

4.1 LayoutManager請求RecyclerView提供指定positionView

ViewHolder是和View相綁定的,同時它也是整個復(fù)用框架的跟蹤單元。在RecyclerView體系中,對ViewHolder采用了二級緩存,分為CacheRecycled Pool,當(dāng)LayoutManagerRecyclerView請求位于某個PositionView時,Recycled View會先去Cache中尋找,如果找到,那么直接返回;如果找不到,那么再去Recycled Pool中尋找,下面就是整個尋找過程的幾種情況:

  • 命中Cache
    這種情況下,不會調(diào)用AdapteronCreateViewHolder或者onBindViewHolder方法:
  • Cache不存在,Recycled Pool也不存在
    這種情況下,會調(diào)用AdapteronCreateViewHolder方法,讓它提供一個對應(yīng)viewTypeViewHolder,我們在其中建立ViewHolderView之間的關(guān)聯(lián)。
  • Cache不存在,Recycled Pool存在
    這種情況下,會回調(diào)AdapteronBindViewHolder方法,我們在其中使用當(dāng)前的數(shù)據(jù)集合來更新ViewHolder所綁定的itemView的狀態(tài)。

4.2 LayoutManager找到對應(yīng)位置的View

LayoutManager通過addView方法把之前找到的View添加進RecyclerViewRecyclerView通過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)不再需要某一個positionView時,它會通知RecyclerViewRecyclerView通過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進行完動畫操作之后,才做detachrecycle的邏輯,這一過程對于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會先通過AdapteronFailedToRecycled(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)。

六、ChildHelperAdapterHelper

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,而是讓ChildHelperItemAnimator去協(xié)調(diào),并由它來操作ViewGroup


最明顯的例子是,假如我們當(dāng)前列表中狀態(tài)為0,1,2,3,此時我們移除了position=0Item,這時候假如刪除的動畫還沒有完成,那么LayoutManagerRecyclerViewgetChildAt(0)返回值將會不同,因為在LayoutManager并不清楚ChildHelper的存在,在它看來,position=0Item已經(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方法還沒有完成,那么AdapterLayoutpostion是不相同的:

七、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 使用完全解析 體驗藝術(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 != finaluse 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 PositionLayout Position并不相等,我們應(yīng)當(dāng)根據(jù)情況選擇需要使用哪個,Adapter Position數(shù)據(jù)所處的位置,而Layout Position則對應(yīng)當(dāng)前View的所處的位置

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 這篇文章分三個部分,簡單跟大家講一下 RecyclerView 的常用方法與奇葩用法;工作原理與ListView比...
    LucasAdam閱讀 4,425評論 0 27
  • 簡介: 提供一個讓有限的窗口變成一個大數(shù)據(jù)集的靈活視圖。 術(shù)語表: Adapter:RecyclerView的子類...
    酷泡泡閱讀 5,218評論 0 16
  • RecyclerView包含以下幾個重要的組件:1.LayoutManager: 測量和布局子View2.Recy...
    烏龜愛吃肉閱讀 3,575評論 4 7
  • 每個夜晚來臨的時候 孤獨總在我左右 在我溫柔的笑容背后 有多少淚水哀愁
    古月陌閱讀 168評論 0 0
  • 當(dāng)你在襁褓的時候, 你發(fā)現(xiàn)依賴他們的懷抱才進入夢鄉(xiāng); 當(dāng)你在童年的時候, 你發(fā)現(xiàn)聽到他們的呼喚才想起回家; 當(dāng)你在...
    義隰閱讀 149評論 0 2