RecyclerView和ListView的區別


RecyclerView和ListView的區別

ListView

Android ListView工作原理完全解析,帶你從源碼的角度徹底理解

郭霖ListView異步加載圖片閃動的問題

ListView緩存機制

RecycleBin機制,RecycleBin定義在AbsListView當中。其中使用View[] mActiveViews存儲View,是屏幕當中正在使用的View,mActiveViews中存儲的View只能被獲取一次;ArrayList [] mScrapViews和ArrayList mCurrentScrap則是用於存儲移動出屏幕后被廢棄的View。當getView方法在適配器中被調用的時候,其中傳入的convertView如果不為空,就是從廢棄的View當中獲得的。

ListView 異步加載圖片出現亂序的原因

ListView 在借助RecycleBin機制的幫助下,實現了一個生產者消費者的模式,不管有多少條數據需要顯示,ListView中的子View其實來來回回就那么幾個,移出屏幕的子View 很快會被移入屏幕的數據重新利用起來, 移出屏幕的view會進入到RecycleBin當中,而新進入屏幕的元素則會從RecycleBin中獲取view ,這就是 RecycleBin 機制。我們來說說產生亂序的原因,每當有新元素進入界面的時候就會回調 getView 方法,如果我們是異步加載圖片的話, 就會在 getView 方法中開啟異步操作從網絡上面獲取圖片,但是異步操作可能是比較耗時的,當我們快速滑動圖片的時候可能會出現這樣一種情況,某一位置上的元素進入屏幕之后開始從網絡上請求圖片,但是還沒等圖片加載到控件上的時候,它就已經被移出了屏幕,又根據 ListView 的工作機制,被移出屏幕的控件會很快被進入屏幕的元素重新利用起來,正是因為這個原因導致了圖片亂序,因為如果這時候前面發起的網絡請求正好得到回應的話, 首先會將剛才請求的圖片顯示到控件上面,雖然他們的位置不同,但是共用同一個顯示圖片的控件,這時候新移進來的元素如果正好也發起一個網絡請求獲取圖片的話,在圖片下載結束就會將圖片顯示到當前控件上面,因此就出現了先顯示一張圖片后又顯示一張圖片的情況了。

解決這個問題的方法有:在 getView 方法中對每個控件調用 setTag 方法,設置其標志,在需要用到控件的地方使用 findViewWithTag 獲得控件,這種方式解決圖片亂序的原理是:因為 ListView 中的控件都是可以重用的,當移出屏幕的控件重新被利用的時候會回調 getView 方法,在 getView 方法中會調用 setTag 方法為當前控件設置標志,那么這個標志將會覆蓋掉原先設置在該控件上面的標志,這樣每次使用 findViewWithTag 獲取對應於舊的 Tag 的控件的時候就只能得到 null 了,而只有非 null 的時候我們才會顯示圖片到控件上面,這樣就保證了不會顯示舊的圖片在控件上面的效果了。

**ListView 優化方案 **

關於 ListView 的優化總結幾點

  • 重用 ConvertView,ConvertView會把之前加載好的布局進行緩存,可以重用,就減少不必要 view 的創建,因為 inflate 操作也是把 xml 實例化為相應的 View 實例,這個過程是屬於 IO 操作,相對來說比較耗時。

  • 減少 findViewById 的次數,因為即使使用了ConvertView以后不會重復加載布局了,但是每次getView方法回調的時候還是通過findViewById 重復創建控件的實例,那么就通過把 xml 文件中的涉及到的控件封裝成內部類ViewHolder的方式, 通過 View 的 setTag 和 getTag 方法將 當前布局View 和對應的 ViewHolder 對象綁定在一起,減少不必要 的 findViewById 次數。

  • 如果涉及到加載圖片之類比較耗時的操作,可以在用戶快速滑動屏幕的時候禁止圖片的加載,等到滑動停止的時候再去加載圖片。

  • ListView 中 Item 的布局層次越簡單越好,主要為了避免布局太深帶來的重繪太復雜問題,還可以減少半透明元素的繪制,因為半透明更加的耗時。

  • 盡量保證 Adapter 適配器的 hasStables 方法返回 true,這樣在調用 notifyDataSetChanged()方法的時候,如果 Item 內容沒有發生變化的話,ListView 將不再重繪,以此達到優化。

  • 直接使用 RecycleView 代替 ListView,每個 Item 自己發生變動,ListView 都會去調用 notifyDataSetChanged 方法更新所有的 Item,未免有點太浪費性能了,而 RecycleView 可以實現每個 Item 的局部刷新,不再需要刷新所有的 Item,同時引入了增加和刪除的動畫 效果,在性能和定制上有很大改善。

  • 盡量開啟硬件加速功能。

  • 當數據量比較大的時候,可以使用分段加載或者分頁加載。

    分段加載。有些情況下需要加載網絡中的數據,顯示到ListView,而且往往此時都是數據量比較多的一種情況,如果數據有1000條,沒有優化過的ListView都是會一次性把數據全部加載出來的,很顯然需要一段時間才能加載出來,我們不可能讓用戶面對着空白的屏幕等好幾分鍾,那么這時我們可以使用分段加載,比如先設置每次加載數據10條,當用戶滑動ListView到底部的時候,我們再加載20條數據出來,然后使用Adapter刷新ListView,這樣用戶只需要等待10條數據的加載時間,這樣也可以緩解一次性加載大量數據而導致OOM崩潰的情況。

    分頁加載。上面分段加載也不能完全解決OOM崩潰的情況,因為雖然我們在分段中一次只增加10條數據到List集合中,然后再刷新到ListView中去,假如有10萬條數據,如果我們順利讀到最后這個List集合中還是會累積海量條數的數據,還是可能會造成OOM崩潰的情況,這時候我們就需要用到分頁,比如說我們將這10萬條數據分為1000頁,每一頁100條數據,每一頁加載時都覆蓋掉上一頁中List集合中的內容,然后每一頁內再使用分批加載,這樣用戶的體驗就會相對好一些。

ListView三級緩存

  • 內存緩存
  • 磁盤緩存
  • 網絡拉取

RecyclerView

RecyclerView 源碼解析

把 RecyclerView 擼成馬蜂窩

RecyclerView緩存機制(咋復用?)

RecyclerView緩存機制

Scrap和Cache分別是通過position去找ViewHolder可以直接復用;

在RecyclerView當中也並不是每次都重新創建ViewHolder對象,不是每次都重新綁定ViewHolder數據,而是通過Recycler來獲得下一個ViewHolder。

RecyclerView使用Recycler管理緩存ViewHolder,對於不同狀態的ViewHolder存儲在了不同的集合中,RecyclerView有四級緩存,分別是ArrayList mAttachedScrap、ArrayList mCachedViews、ViewCacheExtension mViewCacheExtension 和 RecycledViewPool mRecyclerPool,緩存的對象是 ViewHolder

而從RecycledViewPool 中復用的ViewHolder需要重新綁定數據,並且復用的ViewHolder只能復用於ViewType相同的表項,因為RecycledViewPool 對ViewHolder是按照ViewType分類存儲的,RecycledViewPool通過type來獲取ViewHolder,獲取的ViewHolder是個全新,需要重新綁定數據;ViewCacheExtension自定義緩存,目前來說應用場景比較少卻需慎用;從mCachedViews中復用ViewHolder只能復用於指定位置的item;而從mAttachedScrap復用的ViewHolder不需要重新創建也不需要重新綁定數據。

如果四個層級的緩存都沒有命中,才會重新創建ViewHolder對象並且綁定。


ListView和RecyclerView的對比

基礎使用

ListView的適配器最終是繼承BaseAdapter類重寫方法,自定義ViewHolderConvertView一起完成復用優化工作。RecyclerView 同樣也是需要繼承重寫 RecyclerView.Adapter 和 強制使用RecyclerView.ViewHolder ,以及使用到了LayoutManager。

所以這樣看來,在RecyclerView當中與ListView不同之處在於:

  • 對ViewHolder的編寫進行了規范化。另外,RecyclerView對 Item的復用 ,關於使用到ViewHolder對控件實例進行緩存的工作已經封裝好,不需要像 ListView 那樣調用 setTag 。
  • 但是 RecyclerView 多出了 LayoutManager 的設置工作 。

布局效果

  • ListView 做到了數據和視圖的分離,布局排列是自身去管理。
  • 而RecycleView 將視圖和布局進一步分離, 因而出現了 LayoutManager,RecycleView 只負責管理視圖的重復利用,然后將布局的管理全權交給了 LayoutManager, 不像 ListView 那樣被限制在垂直滾動布局樣式 。LayoutManager中制定了一套可擴展的布局排列接口,子類只要按照接口規范來實現,就能定制出各種不同排列方式的布局了。 LayoutManager 是一個抽象類,系統已經為我們提供了三個相關的實現類 LinearLayoutManager(線性布局效果)GridLayoutManager(網格布局效果)StaggeredGridLayoutManager(瀑布流布局效果)。 RecyclerView 默認就能支持 線性布局網格布局瀑布流布局 三種 ,通過配置和切換 LayoutManager 就可以獲得不同的布局效果。所以如果想自定義出更多布局效果,可以繼承重寫自己的LayoutManager,通過LayoutManager還可以設置滾動方向、獲取Item的位置等等。

緩存機制

  • ListView的RecycleBin機制
  • RecyclerView的多級別緩存

事件監聽

  • ListView中提供了setOnItemClickListener、 setOnItemLongClickListener、setOnItemSelectedListener 等幾個用於設置監聽器,但是這些只能注冊的是子項的點擊事件,具體到子項里面的某個控件的點擊事件的處理會比較麻煩,而且添加了 HeaderView 和 FooterView ,ListView 會把 HeaderView 和 FooterView 算入 position 內,代碼有可能會報數組越界的錯誤 。
  • 但是RecyclerView ,它並沒有像 ListView 提供太多關於 Item 的某種事件監聽,需要我們自己通過ViewHolder去給子項具體的View去注冊點擊事件,或者就是通過 addOnItemTouchListener 和GestureDetector手勢判斷來實現

空數據的處理

  • ListView 提供了 setEmptyView 這個 API 來讓我們處理 Adapter 中數據為空的情況。
  • 但是 RecyclerView 並沒有提供這類 API ,所以需要自己處理。

HeaderView 和 FooterView

  • 在 ListView 的設計中,addFooterView 、 addFooterView 可以添加HeaderView 和 FooterView 兩種類型的視圖 。
  • RecyclerView 並沒有提供這類 API。

局部刷新

  • 更新了 ListView 的數據源后需要通過 Adapter 的 notifyDataSetChanged 方法來通知視圖更新變化,調用簡單,但它會重繪每個 Item,實際上卻並不是每個 Item 都需要重繪。

  • 而RecyclerView.Adapter 提供了 notifyItemChanged 用於更新單個 Item View 的刷新,我們可以省去自己寫局部更新的工作。

動畫效果

RecyclerView 支持子項目層次的動畫效果,是通過 ItemAnimation 接口(確定一下)實現的, 可以在 Adapter 中的數據發生變化的時候,通過調用 Adapter 的相關方法激活動畫的產生,就是RecyclerView 在做局部刷新等等的時候有一個漸變的動畫效果 。

嵌套滾動機制

掘金


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM