深入淺出 RecyclerView

原文:http://kymjs.com/code/2016/07/10/01

作者:kymjs張濤


今天推薦給各位的是張濤同學最近的一篇文章,說實話,RecyclerView 的文章挺多的,但像這樣由淺入深,一步步講到源碼實現工作原理的不是那麼多,推薦大家閱讀。


起深入淺出這名字的時候我是慎重又慎重的,生怕被人罵標題黨,寫的什麼破玩意還敢說深入淺出。所以還是請大家不要抱着太高的期望,因爲沒有期望就沒有失望,就像陳潤說的,超預期嘛。全當看小說的心情來看這系列文章了。


這篇文章分幾個部分,簡單跟大家講一下 RecyclerView 的常用方法與奇葩用法;工作原理與 ListView 比較;源碼解析;

常用方法

RecyclerView 與 ListView、GridView 類似,都是可以顯示同一種類型 View 的集合的控件。

首先看看最簡單的用法,四步走:

0.接入

build.gradle 文件中加入

compile 'com.android.support:recyclerview-v7:24.0.0'

1.創建對象

RecyclerView recyclerview = (RecyclerView) findViewById(R.id.recyclerview);

2.設置顯示規則

recyclerview.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));

RecyclerView 將所有的顯示規則交給一個叫 LayoutManager 的類去完成了。

LayoutManager 是一個抽象類,系統已經爲我們提供了三個默認的實現類,分別是 LinearLayoutManager GridLayoutManagerStaggeredGridLayoutManager,從名字我們就能看出來了,分別是:線性顯示、網格顯示、瀑布流顯示。當然你也可以通過繼承這些類來擴展實現自己的 LayougManager

3.設置適配器

recyclerview.setAdapter(adapter);

適配器,同 ListView 一樣,用來設置每個item顯示內容的。

通常,我們寫 ListView 適配器,都是:

  • 首先繼承 BaseAdapter

  • 實現四個抽象方法;

  • 創建一個靜態 ViewHolder

  • getView() 方法中判斷 convertView 是否爲空,創建還是獲取 ViewHolder 對象。


RecyclerView 也是類似的步驟:

  • 首先繼承RecyclerView.Adapter<VH>類;

  • 實現三個抽象方法;

  • 創建一個靜態的 ViewHolder


不過 RecyclerView ViewHolder 創建稍微有些限制,類名就是上面繼承的時候泛型中聲明的類名(或者應該說,上面泛型中的類名需要是這個holder的類名);並且 ViewHolder 必須繼承自RecyclerView.ViewHolder類。

public class DemoAdapter extends RecyclerView.Adapter<DemoAdapter.VH> {
 
 private
List<Data> dataList;
 private Context context;

 public DemoAdapter(Context context, ArrayList<Data> datas) {
   this.dataList = datas;
   this.context = context;  }

 @Override  public VH onCreateViewHolder(ViewGroup parent, int viewType) {
   return new VH(View.inflate(context, android.R.layout.simple_list_item_2, null));  }

 @Override  public void onBindViewHolder(VH holder, int position) {    holder.mTextView.setText(dataList.get(position).getNum());  }

 @Override  public int getItemCount() {
   return dataList.size();
 }
 
 public static class VH extends RecyclerView.ViewHolder {
   
   TextView mTextView;
   
   public VH(View itemView) {
     mTextView = (TextView) itemView.findViewById(android.R.id.text1);
   }  }
}

更多方法

除了常用方法,當然還有不常用的。

瀑布流與滾動方向

前面已經介紹過,RecyclerView實現瀑布流,可以通過一句話設置:recycler.setLayoutManager(new StaggeredGridLayoutManager(2, VERTICAL))就可以了。

其中 StaggeredGridLayoutManager 第一個參數表示列數,就好像 GridView 的列數一樣,第二個參數表示方向,可以很方便的實現橫向滾動或者縱向滾動。

使用 demo 可以查看:Github 【RecyclerView簡單使用

添加刪除 item 的動畫

ListView 每次修改了數據源後,都要調用 notifyDataSetChanged() 刷新每項 item 類似,只不過 RecyclerView 還支持局部刷新notifyItemInserted(index)、
notifyItemRemoved(position)、
notifyItemChanged(position)。

在添加或刪除了數據後,RecyclerView 還提供了一個默認的動畫效果,來改變顯示。同時,你也可以定製自己的動畫效果:模仿 DefaultItemAnimator 或直接繼承這個類,實現自己的動畫效果,並調用recyclerview.setItemAnimator(new DefaultItemAnimator()); 設置上自己的動畫。

使用 demo 可以查看:Github 【RecyclerView默認動畫

LayoutManager的常用方法

  • findFirstVisibleItemPosition() 返回當前第一個可見 Item 的 position

  • findFirstCompletelyVisibleItemPosition() 返回當前第一個完全可見 Item 的 position

  • findLastVisibleItemPosition() 返回當前最後一個可見 Item 的 position

  • findLastCompletelyVisibleItemPosition() 返回當前最後一個完全可見 Item 的 position.

  • scrollBy() 滾動到某個位置。

adapter封裝

其實很早之前寫過一篇關於 RecyclerView 適配器的封裝,所以這不再贅述了,傳送門:RecyclerView的通用適配器

使用 demo 可以查看:Github 【RecyclerView通用適配器演示

吐槽

OnItemTouchListener 什麼鬼?

用習慣了 ListView OnItemClickListenerRecyclerView 你的 OnItemClickListener 呢?

Tell me where do I find, something like ListView listener ?

好吧,翻遍了 API 列表,就找到了個 OnItemTouchListener ,這特麼什麼鬼,我幹嘛要對每個 item 監聽觸摸屏事件。

萬萬沒想到,最終我還是在 Google IO 裏面的介紹找到了原因。原來是 Google 的工程師分不清究竟是改給 listview 的 item 添加點擊事件,還是應該給每個 item 的 view 添加點擊事件,索性就不給 OnItemClickListener 了,然後在 support demo 裏面,你就會發現,RecyclerView 的 item 點擊事件都是寫在了 adapter 的 ViewHolder 裏面。

當然,除了 support demo 包裏面使用的在 ViewHolder 裏面設置點擊事件以外,我還寫好了一個 RecyclerView 使用的 OnItemClickListener 代碼請見:RecyclerItemClickListener.java

需要一提的是,網上有很多這種類似的 ItemClickListener ,在使用的時候一定注意一個問題,就是循環引用問題。比如 listener 裏面持有了一個 recyclerview, 而這個 recyclerview 在調用 setListener() 的時候又持有了一個 listener。儘管 Java 虛擬機現在可以解決這種問題了,但作爲代碼編寫者,這種寫法還是應該儘量避免的。

divider 跑哪了?

ListView中設置 divider 非常簡單,只需要在 XML 文件中設置就可以了,同時還可以設置 divider 高度。

android:divider="@android:color/black"
android:dividerHeight="2dp"

而在RecyclerView裏面,想實現這兩種需求,稍微複雜一點,需要自己繼承RecyclerView.ItemDecoration來實現想要實現的方法。

雖說這樣寫靈活多了,但是要額外寫一個類去做難免麻煩,這裏大家可以看我已經實現好的一個封裝,包括顯示純色divider顯示圖片dividerdivider的上下左右的間距寬高設置 應該可以滿足基本需求了:Divider.java

使用 demo 可以查看:Github 【自定義 Divider 使用

五虎上將工作原理

借用 Google IO 視頻中的一張截圖:
視頻的完整地址可查看: RecyclerView ins and outs - Google I/O 2016


其實上圖中並沒有寫完整,大 boss RecyclerView 應該有這五虎上將:


LayoutManager工作原理

java.lang.Object
   ↳ android.view.View
        ↳ android.view.ViewGroup
            ↳ android.support.v7.widget.RecyclerView

首先是 RecyclerView 繼承關係,可以看到,與 ListView 不同,他是一個 ViewGroup。既然是一個 View,那麼就不可少的要經歷 onMeasure()onLayout()onDraw() 這三個方法。

實際上,RecyclerView 就是將 onMeasure()onLayout() 交給了 LayoutManager 去處理,因此如果給 RecyclerView 設置不同的 LayoutManager 就可以達到不同的顯示效果,因爲onMeasure()onLayout()都不同了嘛。

ItemDecoration 工作原理

ItemDecoration 是爲了顯示每個 item 之間分隔樣式的。它的本質實際上就是一個 Drawable。當 RecyclerView 執行到 onDraw() 方法的時候,就會調用到他的 onDraw(),這時,如果你重寫了這個方法,就相當於是直接在 RecyclerView 上畫了一個 Drawable 表現的東西。

而最後,在他的內部還有一個叫getItemOffsets()的方法,從字面就可以理解,他是用來偏移每個 item 視圖的。當我們在每個 item 視圖之間強行插入繪畫了一段 Drawable,那麼如果再照着原本的邏輯去繪 item 視圖,就會覆蓋掉 Decoration 了,所以需要getItemOffsets()這個方法,讓每個 item 往後面偏移一點,不要覆蓋到之前畫上的分隔樣式了。

ItemAnimator

每一個 item 在特定情況下都會執行的動畫。說是特定情況,其實就是在視圖發生改變,我們手動調用notifyxxxx()的時候。通常這個時候我們會要傳一個下標,那麼從這個標記開始一直到結束,所有 item 視圖都會被執行一次這個動畫。

Adapter工作原理

首先是適配器,適配器的作用都是類似的,用於提供每個 item 視圖,並返回給 RecyclerView 作爲其子佈局添加到內部。

但是,與 ListView 不同的是,ListView 的適配器是直接返回一個 View,將這個 View 加入到 ListView 內部。而 RecyclerView 是返回一個 ViewHolder 並且不是直接將這個 holder 加入到視圖內部,而是加入到一個緩存區域,在視圖需要的時候去緩存區域找到 holder 再間接的找到 holder 包裹的 View。

ViewHolder

每個 ViewHolder 的內部是一個 View,並且 ViewHolder 必須繼承自RecyclerView.ViewHolder類。
這主要是因爲 RecyclerView 內部的緩存結構並不是像 ListView 那樣去緩存一個 View,而是直接緩存一個 ViewHolder ,在 ViewHolder 的內部又持有了一個 View。既然是緩存一個 ViewHolder,那麼當然就必須所有的 ViewHolder 都繼承同一個類才能做到了。

緩存與複用的原理

還是一張截圖

RecyclerView 的內部維護了一個二級緩存,滑出界面的 ViewHolder 會暫時放到 cache 結構中,而從 cache 結構中移除的 ViewHolder,則會放到一個叫做 RecycledViewPool 的循環緩存池中。

順帶一說,RecycledView 的性能並不比 ListView 要好多少,它最大的優勢在於其擴展性。但是有一點,在 RecycledView 內部的這個第二級緩存池 RecycledViewPool 是可以被多個 RecyclerView 共用的,這一點比起直接緩存 View 的 ListView 就要高明瞭很多,但也正是因爲需要被多個 RecyclerView 公用,所以我們的 ViewHolder 必須繼承自同一個基類(即RecyclerView.ViewHolder)。

默認的情況下,cache 緩存 2 個 holder,RecycledViewPool 緩存 5 個 holder。對於二級緩存池中的 holder 對象,會根據 viewType 進行分類,不同類型的 viewType 之間互不影響。

源碼分析

這部分代碼較多,不便在微信平臺閱讀,因此略去,有興趣深入瞭解的同學可以點擊閱讀原文前往閱讀。

Demo & PPT

寫了這麼多累死我了,就這樣吧,最後發一個 demo 地址:RecyclerViewDemo

和一份內部分享的 PPT 地址:RecyclerView PPT

發佈了7 篇原創文章 · 獲贊 3 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章