RecyclerView技術棧

applemax82 8年前發布 | 15K 次閱讀 Android開發 移動開發

來自: http://www.jcodecraeer.com//a/anzhuokaifa/androidkaifa/2016/0307/4031.html


原文:小?子的簡書 

概述

隨著2014年Google IO的召開,Android L Preview版隨之發布,對于開發著來說,帶來了性能上的改善,而對于消費者來說,得到了體驗上的提升。我想,無論是開發者還是使用者,一定都非常喜歡這次的版本跟新。

同時,這次也帶來了兩個全新的View控件:RecyclerViewCardView。這篇文章將重點介紹RecyclerView,它有許多內部類和接口。接下來,我將介紹它們的功能,已經如何使用。

當然,在這之前,我要聲明的是:RecyclerView 是Support Library的一部分。所以只需要在app/build.gradle中添加以下依賴,便能立即使用:

dependencies {
    compile 'com.android.support:recyclerview-v7:23.2.0'
}

然后點擊“Sync Project with Gradle files”,讓IDE去下載適當的資源文件。

為什么命名為RecyclerView?

先讓我們來看看Google在L Preview中是如何定義RecyclerView的:

A flexible view for providing a limited window into a large data set.
(能夠在有限的窗口中展示大數據集合的靈活視圖。)

所以我們能夠理解為,RecyclerView一個恰當的使用場景是:由于尺寸限制,用戶的設備不能一次性展現所有條目,用戶需要上下滾動以查看更多條目。滾出可見區域的條目將被回收,并在下一個條目可見的時候被復用。

我們可以從下圖中得到更直觀的解釋:


左邊的圖是數據初始化后的示例,當向上滾動視圖的時候,當條目不可見之后將被回收。右圖中紅色區域內的兩條不可見條目,將被放到緩存隊列中以便新的條目可見時進行復用。

對于減少內存開銷和CPU的計算,緩存條目是一個非常有用的方法,因為這意味著我們不必每次都創建新的條目,從而減小內存開銷和CPU的計算,而且還能夠有效降低屏幕的卡頓,保證滑動的順滑和16ms準則。

看到這里,你可能不禁會問:并沒有什么新東西啊,這和ListView有什么區別呀?我們已經使用ListView很長一段時間了呀,它一樣可以做到呀。不過,視圖回收本身并不是什么新鮮事。但是回想之前我們寫的ListView,無論從它的的性能表現著手,還是語法的書寫,甚至數據的綁定都未免略顯臃腫。那么現在,我們將再也不會出現上述癥狀,因為Google提供了一個更好,更靈活的控件――RecyclerView

OK,從現在開始,讓我們一步一步,開始了解它。

結構

如果你想使用RecyclerView,需要做以下操作:

  • RecyclerView.Adapter - 處理數據集合并負責綁定視圖

  • ViewHolder - 持有所有的用于綁定數據或者需要操作的View

  • LayoutManager - 負責擺放視圖等相關操作

  • ItemDecoration - 負責繪制Item附近的分割線

  • ItemAnimator - 為Item的一般操作添加動畫效果,如,增刪條目等

我們可以從下圖更直觀的了解到RecyclerView的基本結構:


由此可見,想要在ListView中實現條目的增刪動畫是一件非常困難的事情,但是RecyclerView為我們提供了很好的便利。而且RecyclerView增強了ViewHolder設計模式,這在當前所使用的ListView中是不曾有的。

與傳統ListView比較

RecyclerView與老前輩ListView的不同點,主要在于以下幾個特性:

  • Adapter中的ViewHolder模式 - 對于ListView來說,通過創建ViewHolder來提升性能并不是必須的。因為ListView并沒有嚴格的ViewHolder設計模式。但是在使用RecyclerView的時候,Adapter必須實現至少一個ViewHolder,必須遵循ViewHolder設計模式。

  • 定制Item條目 -  ListView只能實現垂直線性排列的列表視圖,與之不同的是,RecyclerView可以通過設置RecyclerView.LayoutManager來定制不同風格的視圖,比如水平滾動列表或者不規則的瀑布流列表。

  • Item動畫 - 在ListView中沒有提供任何方法或者接口,方便開發者實現Item的增刪動畫。相反地,可以通過設置RecyclerViewRecyclerView.ItemAnimator來為條目增加動畫效果。

  • 設置數據源 - 在LisView中針對不同數據封裝了各種類型的Adapter,比如用來處理數組的ArrayAdapter和用來展示Database結果的CursorAdapter。相反地,在RecyclerView中必須自定義實現RecyclerView.Adapter并為其提供數據集合。

  • 設置條目分割線 - 在ListView中可以通過設置android:divider屬性來為兩個Item間設置分割線。如果想為RecyclerView添加此效果,則必須使用RecyclerView.ItemDecoration,這種實現方式不僅更靈活,而且樣式也更加豐富。

  • 設置點擊事件 - 在ListView中存在AdapterView.OnItemClickListener接口,用來綁定條目的點擊事件。但是,很遺憾的是在RecyclerView中,并沒有提供這樣的接口,不過,提供了另外一個接口RcyclerView.OnItemTouchListener,用來響應條目的觸摸事件。

RecyclerView組件

RecyclerView.Adapter

確切的說,Adapter扮演著兩個角色。一是,根據不同ViewType創建與之相應的的Item-Layout,二是,訪問數據集合并將數據綁定到正確的View上。這就需要我們重寫以下兩個函數:

  • public VH onCreateViewHolder(ViewGroup parent, int viewType) 創建Item視圖,并返回相應的ViewHolder

  • public void onBindViewHolder(VH holder, int position) 綁定數據到正確的Item視圖上。

另外我們還需要重寫另一個方法,像ListView-Adapter那樣,同樣地告訴RecyclerView-Adapter列表Items的總數:

  • public int getItemCount() 返回該Adapter所持有的Itme數量

RecyclerView.ViewHolder

ViewHolder的基本用法是用來存放View對象。Android團隊很早之前就推薦使用“ViewHolder設計模式”,但實際上他們并沒有把這種概念強加給開發者,而且也沒有要求開發者在Adapter中必須使用ViewHolder pattern。那么現在對于這種新型的RecyclerView.Adapter,我們必須實現并使用它。

另外值得一提的是,可以通過打印ViewHolder.toString來獲取更多有效信息:

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("ViewHolder{" +
                Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId +
                ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition);
        if (isScrap()) sb.append(" scrap");
        if (isInvalid()) sb.append(" invalid");
        if (!isBound()) sb.append(" unbound");
        if (needsUpdate()) sb.append(" update");
        if (isRemoved()) sb.append(" removed");
        if (shouldIgnore()) sb.append(" ignored");
        if (isChanged()) sb.append(" changed");
        if (isTmpDetached()) sb.append(" tmpDetached");
        if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")");
        if (isAdapterPositionUnknown()) sb.append("undefined adapter position");
        if (itemView.getParent() == null) sb.append(" no parent");
        sb.append("}");
        return sb.toString();
    }

因此,一個基本的RecyclerView.Adapter如下:

public class SimplerItemAdapter extends RecyclerView.Adapter<SimplerItemAdapter.SimpleItemViewHolder> {

  private List<String> items;

  public SimplerItemAdapter(@NonNull List<String> dateItems) {
    this.items = (dateItems != null ? dateItems : new ArrayList<String>());
  }

  @Override public SimpleItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
    View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
    return new SimpleItemViewHolder(itemView);
  }

  @Override public void onBindViewHolder(SimpleItemViewHolder viewHolder, int position) {
    viewHolder.textView.setText(items.get(position));
  }

  @Override public int getItemCount() {
    return (this.items != null) ? this.items.size() : 0;
  }

  protected final static class SimpleItemViewHolder extends RecyclerView.ViewHolder {
    protected TextView textView;

    public SimpleItemViewHolder(View itemView) {
      super(itemView);
      this.textView = (TextView) itemView.findViewById(R.id.text);
    }
  }
}

RecyclerView.LayoutManager

LayoutManager的職責是擺放Item的位置,并且負責決定何時回收和重用Item。

必須為RecyclerView指定LayoutManager,否則會出現以下異常:

 AndroidRuntime java.lang.NullPointerException: Attempt to invoke virtual method ‘void android.support.v7.widget.RecyclerView$LayoutManager.onMeasure(android.support.v7.widget.RecyclerView$Recycler, android.support.v7.widget.RecyclerView$State, int, int)’ on a null object reference
  • LinearLayoutManager 水平或者垂直的Item視圖。

  • GridLayoutManager 網格Item視圖。

  • StaggeredGridLayoutManager 交錯的網格Item視圖。

當然還有一些很實用的API:

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

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

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

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

LayoutManager當前有且僅有一個抽象函數:

public LayoutParams generateDefaultLayoutParams()

另外值得注意的是,自定義LayoutManager還應該實現以下方法:

   /**
     * Scroll to the specified adapter position.
     *
     * Actual position of the item on the screen depends on the LayoutManager implementation.
     * @param position Scroll to this adapter position.
     */
    public void scrollToPosition(int position) {
        if (DEBUG) {
            Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract");
        }
    }

RecyclerView.ItemDecoration

通過設置recyclerView.addItemDecoration(new DividerDecoration(this));來改變Item之間的偏移量或者對Item進行裝飾。

當然,你也可以對RecyclerView設置多個ItemDecoration,列表展示的時候會遍歷所有的ItemDecoration并調用里面的繪制方法,對Item進行裝飾。

RecyclerView.ItemDecoration是一個抽象類,可以通過重寫以下三個方法,來實現Item之間的偏移量或者裝飾效果:

  • public void onDraw(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之前調用,所以這有可能被Item的內容所遮擋

  • public void onDrawOver(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之后調用,因此裝飾將浮于Item之上

  • public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 與padding或margin類似,LayoutManager在測量階段會調用該方法,計算出每一個Item的正確尺寸并設置偏移量。

RecyclerView.ItemAnimator

ItemAnimator能夠幫助Item實現獨立的動畫。

ItemAnimator作觸發于以下三種事件:

  1. 某條數據被插入到數據集合中

  2. 從數據集合中移除某條數據

  3. 更改數據集合中的某條數據

幸運的是,在Android中默認實現了一個DefaultItemAnimator,我們可以通過以下代碼為Item增加動畫效果:

recyclerView.setItemAnimator(new DefaultItemAnimator());

在之前的版本中,當時據集合發生改變時,我們通過調用.notifyDataSetChanged(),來刷新列表,因為這樣做會觸發列表的重繪,所以并不會出現任何動畫效果,因此需要調用一些以notifyItem*()作為前綴的特殊方法,比如:

  • public final void notifyItemInserted(int position) 向指定位置插入Item

  • public final void notifyItemRemoved(int position) 移除指定位置Item

  • public final void notifyItemChanged(int position) 更新指定位置Item

Listeners

很遺憾,RecyclerView并沒有像ListView那樣提供以下兩個Item的點擊監聽事件

  • public void setOnItemClickListener(@Nullable OnItemClickListener listener) Item點擊事件監聽

  • public void setOnItemLongClickListener(OnItemLongClickListener listener) Item長按事件監聽

但是存在這樣一個觸摸事件的監聽RecyclerView.OnItemTouchListener雖然變得更靈活,但是對應的代碼量和書寫難度卻有了一定的增長,至少對我是這樣的。

至此,所有與本文章相關的代碼都可以從Github上獲取到,另外這個倉庫中還有一份本人精心制作的PPT,可供參考。

參考資料:

Codepath - Codepath Website

A First Glance at Android’s RecyclerView - WolframRittmeyer

 本文由用戶 applemax82 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!