似曾相識的 RecyclerView

一.經常使用方法java

  RecyclerView 與 ListView、GridView 相似,都是能夠顯示同一種類型 View 的集合的控件。
    首先看看最簡單的用法,四步走:android

  ①接入 build.gradle 文件中加入git

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

  ②建立對象github

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

  ③設置顯示規則緩存

recyclerview.setLayoutManager(

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

  RecyclerView 將全部的顯示規則交給一個叫 LayoutManager 的類去完成了。
  LayoutManager 是一個抽象類,系統已經爲咱們提供了三個默認的實現類,分別是 LinearLayoutManager、 GridLayoutManager 、 StaggeredGridLayoutManager,從名字咱們就能看出來了,分別是,線性顯示、網格顯示、瀑布流顯示。固然你也能夠經過繼承這些類來擴展實現本身的 LayougManager。ide

  ④ 設置適配器佈局

recyclerview.setAdapter(adapter);

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

  一般,咱們寫 ListView 適配器,都是首先繼承 BaseAdapter,實現四個抽象方法,建立一個靜態ViewHolder , getView() 方法中判斷 convertView 是否爲空,建立仍是獲取 viewholder 對象。gradle

  而 RecyclerView 也是相似的步驟,首先繼承RecyclerView.Adapter<VH>類,實現三個抽象方法,建立一個靜態的 ViewHolder。不過 RecyclerView 的 ViewHolder 建立稍微有些限制,類名就是上面繼承的時候泛型中聲明的類名(好像反了,應該是上面泛型中的類名應該是這個holder的類名);而且 ViewHolder 必須繼承自RecyclerView.ViewHolder類。動畫

 1 public class DemoAdapter extends RecyclerView.Adapter<DemoAdapter.VH> {
 2     private List<Data> dataList;
 3     private Context context;
 4 
 5     public DemoAdapter(Context context, ArrayList<Data> datas) {
 6         this.dataList = datas;
 7         this.context = context;
 8     }
 9 
10     @Override
11     public VH onCreateViewHolder(ViewGroup parent, int viewType) {
12         return new VH(View.inflate(context, android.R.layout.simple_list_item_2, null));
13     }
14 
15     @Override
16     public void onBindViewHolder(VH holder, int position) {
17         holder.mTextView.setText(dataList.get(position).getNum());
18     }
19 
20     @Override
21     public int getItemCount() {
22         return dataList.size();
23     }
24 
25     public static class VH extends RecyclerView.ViewHolder {
26         TextView mTextView;
27         public VH(View itemView) {
28             super(itemView);
29             mTextView = (TextView) itemView.findViewById(android.R.id.text1);
30         }
31     }

  一些不經常使用的方法:

  • 瀑布流與滾動方向

    前面已經介紹過,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通用適配器演示

 

二.工做原理與ListView比較

 

類名 做用
RecyclerView.LayoutManager 負責Item視圖的佈局的顯示管理
RecyclerView.ItemDecoration 給每一項Item視圖添加子View,例如能夠進行畫分隔線之類
RecyclerView.ItemAnimator 負責處理數據添加或者刪除時候的動畫效果
RecyclerView.Adapter 爲每一項Item建立視圖
RecyclerView.ViewHolder 承載Item視圖的子佈局

一、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 之間互不影響。

 

三 .源碼解析

一、onMeasure

  既然是一個 View,咱們先從onMeasure()開始看。
  以前咱們就說了 RecyclerView 的 measure 和 layout 都是交給了 LayoutManager 去作的,來看一下爲何:

1 if (mLayout.mAutoMeasure) {
2     final int widthMode = MeasureSpec.getMode(widthSpec);
3     final int heightMode = MeasureSpec.getMode(heightSpec);
4     final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
5             && heightMode == MeasureSpec.EXACTLY;
6     mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
7 } else {
8     mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
9 }

  不管是否啓用 mAutoMeasure 最終都會執行到 mLayout.onMeasure() 方法中,而這個 mLayout 就是一個 LayoutManager 對象。

  咱們挑選 LinearLayoutManager 來看
  發現它並無onMeasure()方法,LinearLayoutManager 直接繼承自 LayoutManager,因此又回到了父類 LayoutManager 中。

 1 void defaultOnMeasure(int widthSpec, int heightSpec) {
 2     // calling LayoutManager here is not pretty but that API is already public and it is better
 3     // than creating another method since this is internal.
 4     final int width = LayoutManager.chooseSize(widthSpec,
 5             getPaddingLeft() + getPaddingRight(),
 6             ViewCompat.getMinimumWidth(this));
 7     final int height = LayoutManager.chooseSize(heightSpec,
 8             getPaddingTop() + getPaddingBottom(),
 9             ViewCompat.getMinimumHeight(this));
10 
11     setMeasuredDimension(width, height);
12 }

  有一句很是奇葩的註釋:在這裏直接調用 LayoutManager 靜態方法並不完美,由於自己就是在類內部,更好的辦法調用一個單獨的方法。但反正這段代碼也已經公開了,大家本身看着辦。。。。。。
  若是這不是歷史遺留問題,那確定是臨時工寫的,你寫的時候都意識到這問題了,你還把一大堆類都寫在一個類裏面,形成了 RecyclerView 一個類有一萬多行代碼。我猜你是爲了類之間跨類調用方便一點,但是你就不能設置一個包訪問權限,全部類成員方法都包內調用嗎,一個類幹了六個類的活,網上竟然還有人說這是高內聚的表現。

  接着是chooseSize()方法,很簡單,直接根據測量值和模式返回了最適大小。

 1 public static int chooseSize(int spec, int desired, int min) {
 2     final int mode = View.MeasureSpec.getMode(spec);
 3     final int size = View.MeasureSpec.getSize(spec);
 4     switch (mode) {
 5         case View.MeasureSpec.EXACTLY:
 6             return size;
 7         case View.MeasureSpec.AT_MOST:
 8             return Math.min(size, Math.max(desired, min));
 9         case View.MeasureSpec.UNSPECIFIED:
10         default:
11             return Math.max(desired, min);
12     }
13 }

  緊接着是對子控件 measure ,調用了:dispatchLayoutStep2() 調用了相同的方法,子控件的 measure 在 layout 過程當中講解

二、onLayout

  而後咱們來看 layout 過程. 在onLayout()方法中間接的調用到了這麼一個方法:dispatchLayoutStep2(),在它之中又調用到了mLayout.onLayoutChildren(mRecycler, mState); 
  咱們重點看這個onLayoutChildren()方法。 
  這個方法在 LayoutManager 中的實現是空的,那麼想必是在子類中實現了吧。仍是找LinearLayoutManager ,跟上面 measure 過程同樣,調用了dispatchLayoutStep2() 跟進去發現這麼一個方法:

fill(recycler, mLayoutState, state, false);

  onLayoutChildren() 中有一個很是重要的方法:fill()
  recycler,是一個全局的回收複用池,用於對每一個itemview回收以及複用提供支持。稍後會詳細講這個。

 1 while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
 2     layoutChunk(recycler, state, layoutState, layoutChunkResult);
 3     layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
 4 
 5     if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
 6         layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
 7         if (layoutState.mAvailable < 0) {
 8             layoutState.mScrollingOffset += layoutState.mAvailable;
 9         }
10         recycleByLayoutState(recycler, layoutState);
11     }
12 }

  fill() 做用就是根據當前狀態決定是應該從緩存池中取 itemview 填充 仍是應該回收當前的 itemview。 
  其中,layoutChunk() 負責從緩存池 recycler 中取 itemview,並調用View.addView() 將獲取到的 ItemView 添加到 RecyclerView 中去,並調用 itemview 自身的 layout 方法去佈局 item 位置。
  同時在這裏,還調用了measureChildWithMargins()來測繪子控件大小以及設置顯示位置。這一步,咱們到下面的 draw 過程還要講。
  而這所有的添加邏輯都放在一個 while 循環裏面,不停的添加 itemview 到 recyclerview 裏面,直到塞滿全部可見區域爲止。

三、onDraw

1 @Override
2 public void onDraw(Canvas c) {
3     super.onDraw(c);
4     final int count = mItemDecorations.size();
5     for (int i = 0; i < count; i++) {
6         mItemDecorations.get(i).onDraw(c, this, mState);
7     }
8 }

  在 onDraw() 中,除了繪製本身之外,還多調了一個mItemDecorations 的 onDraw() 方法,這個mItemDecorations 就是前面吐槽的分隔線的集合。
以前在講 RecyclerView 的五虎上將的時候就講過這個 ItemDecoration。 當時咱們還重寫了一個方法叫getItemOffsets()目的是爲了避免讓 itemview 擋住分隔線。那他是在哪調用的呢? 
  還記得 layout 時說的那個measureChildWithMargins()嗎,就是在這裏:

1  public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
2     final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
3     widthUsed += insets.left + insets.right;
4     heightUsed += insets.top + insets.bottom;
5 
6     if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
7         child.measure(widthSpec, heightSpec);
8     }
9 }

  在 itemview measure 的時候,會把偏移量也計算進來,也就是說:其實 ItemDecoration 的寬高是計算在 itemview 中的,只不過 itemview 自己繪製區域沒有那麼大,留出來的地方正好的透明的,因而就透過 itemview 顯示出了 ItemDecoration。那麼就頗有意思了,若是我故意在 ItemDecoration 的偏移量中寫成0,那麼 itemview 就會擋住 ItemDecoration,而在 itemview 的增長或刪除的時候,會短暫的消失(透明),這時候就又能夠透過 itemview 看到 ItemDecoration 的樣子。使用這種組合還能夠作出意想不到的動畫效果。

相關文章
相關標籤/搜索