RecyclerView 知識梳理(2) Adapter

1、概述

當咱們使用RecyclerView時,第一件事就是要繼承於RecyclerView.Adapter,實現其中的抽象方法,來處理數據的展現邏輯,今天,咱們就來介紹一下Adapter中的相關方法。android

2、基礎用法

咱們從一個簡單的線性列表佈局開始,介紹RecyclerView.Adapter的基礎用法。 首先,須要導入遠程依賴包:git

compile'com.android.support:recyclerview-v7:25.3.1'
複製代碼

接着,繼承於RecyclerView.Adapter來實現自定義的NormalAdaptergithub

public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.NormalViewHolder> {

    private List<String> mTitles = new ArrayList<>();

    public NormalAdapter(List<String> titles) {
        mTitles = titles;
    }

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

    @Override
    public void onBindViewHolder(NormalViewHolder holder, int position) {
        holder.setTitle(mTitles.get(position));
    }

    @Override
    public int getItemCount() {
        return mTitles.size();
    }

    class NormalViewHolder extends RecyclerView.ViewHolder {

        private TextView mTextView;

        NormalViewHolder(View itemView) {
            super(itemView);
            mTextView = (TextView) itemView.findViewById(R.id.tv_title);
        }

        void setTitle(String title) {
            mTextView.setText(title);
        }

    }
}
複製代碼

當咱們實現本身的Adapter時,至少要作四個工做:bash

  • 第一:繼承於RecyclerView.ViewHolder,編寫本身的ViewHolder
  • 這個子類用來描述RecyclerView中每一個Item的佈局以及和它關聯的數據,它同時也是RecyclerView.Adapter<VH>中須要指定的VH類型。
  • 在構造方法中,除了須要調用super(View view)方法來傳入Item的跟佈局來給基類中itemView變量賦值,還應當提早執行findViewById來得到其中的子View以便咱們以後對它們進行更新。
  • 第二:實現onCreateViewHolder(ViewGroup parent, int viewType)
  • RecyclerView須要咱們提供類型爲viewType的新ViewHolder時,會回調這個方法。
  • 在這裏,咱們實例化出了Item的根佈局,並返回一個和它綁定的ViewHolder
  • 第三:實現onBindViewHolder(VH viewHolder, int position)
  • RecyclerView須要展現對應position位置的數據時會回調這個方法。
  • 經過viewHolder中持有的對應position上的View,咱們能夠更新視圖。
  • 第四:實現getItemCount()
  • 返回Item的總數。

Activity中,咱們給Adapter傳遞數據,使用方法和ListView基本相同,只是多了一句在設置LayoutManager的操做,這個咱們後面再分析。ide

private void init() {
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
        mTitles = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            mTitles.add("My name is " + i);
        }
        NormalAdapter normalAdapter = new NormalAdapter(mTitles);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(normalAdapter);
    }
複製代碼

這樣,一個RecyclerView的例子就完成了: 佈局

3、只有一種ViewType下的複用狀況分析

下面,咱們來分析一下兩個關鍵方法的調用時機:post

  • onCreateViewHolder
  • onBindViewHolder

經過這兩個方法回調的時機,咱們能夠對RecyclerView複用的機制有一個大概的瞭解。ui

3.1 初始進入

剛開始進入界面的時候,咱們只展現了3Item,此時這兩個方法的調用狀況以下,能夠看到,RecyclerView只實例化了屏幕內可見的ViewHolder,而且onBindViewHolder是在對應的onCreateViewHolder調用完後當即調用的: this

3.2 開始滑動

當咱們手指觸摸到屏幕,並開始向下滑動,咱們會發現,雖然position=3Item尚未展現出來,可是這時候它的onCreateViewHolderonBindViewHolder就被回調了,也就是說,咱們會預加載一個屏幕之外的Itemspa

3.3 繼續滑動

當咱們繼續往下滑動,position=3Item一被展現,那麼position=4Item的兩個方法就會被回調。

3.4 複用

postion=6Item被展現以後,按照前面的分析,這時候就應當回調position=7onCreateViewHolderonBindViewHolder方法了,可是咱們發現,這時候只回調了onBindViewHolder方法,而傳入的ViewHolder實際上是position=0ViewHolder,也就是咱們所說的複用:

此時,屏幕中 Items的展示狀況爲:
目前不可見的 Itemposition=0,1,2,因此,咱們能夠得出結論:在單一佈局的狀況, RecyclerView在複用的時候,會取相反方向中超出顯示範圍的第 3Item來複用,而並非超出顯示範圍的第一個 Item進行復用。

4、多種類型的佈局

4.1 基本使用

當咱們須要在列表當中展現不一樣類型的Item時,咱們通常須要重寫下面的方法,告訴RecyclerView在對應的position上須要展現什麼類型的Item

  • public int getItemViewType(int position)

RecyclerView在回調onCreateViewHolder的時候,同時也會把viewType傳遞進來,咱們根據viewType來建立不一樣的佈局。 下面,咱們就來演示一下它的用法,這裏咱們返回三種不一樣類型的item

public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.NormalViewHolder> {

    private List<String> mTitles = new ArrayList<>();

    public NormalAdapter(List<String> titles) {
        mTitles = titles;
    }

    @Override
    public NormalViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = null;
        switch (viewType) {
            case 0:
                itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_normal_item_1, parent, false);
                break;
            case 1:
                itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_normal_item_2, parent, false);
                break;
            case 2:
                itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_normal_item_3, parent, false);
                break;

        }
        NormalViewHolder viewHolder = new NormalViewHolder(itemView);
        Log.d("NormalAdapter", "onCreateViewHolder, address=" + viewHolder.toString());
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(NormalViewHolder holder, int position) {
        Log.d("NormalAdapter", "onBindViewHolder, address=" + holder.toString() + ",position=" + position);
        int viewType = getItemViewType(position);
        String title = mTitles.get(position);
        holder.setTitle1("title=" + title + ",viewType=" + viewType);
    }

    @Override
    public int getItemCount() {
        return mTitles.size();
    }

    @Override
    public int getItemViewType(int position) {
        return position % 3;
    }

    class NormalViewHolder extends RecyclerView.ViewHolder {

        private TextView mTv1;

        NormalViewHolder(View itemView) {
            super(itemView);
            mTv1 = (TextView) itemView.findViewById(R.id.tv_title_1);
        }

        void setTitle1(String title) {
            mTv1.setText(title);
        }

    }
}
複製代碼

最終,會獲得下面的界面:

4.2 多種viewType下的複用狀況分析

前面,咱們已經研究過一種viewType下的複用狀況,如今,咱們再來分析一下多種viewType時候的複用狀況。

4.2.1 初始進入

此時,咱們屏幕中展現了postion=0~6這七個ItemonCreateViewHolderonBindViewHolder的回調和以前相同,只會生成屏幕內可見的ViewHolder

4.2.2 開始滑動和繼續滑動

這兩種狀況都和單個viewType時相同,會預加載屏幕之外的一個Item

4.2.3 複用

關鍵,咱們看一下什麼時候會複用position=0/viewType=1Item

此時,屏幕內最上方的 Itemposition=4/viewType=1,最下方的 Itemposition=11/viewType=2,按照以前的分析, RecyclerView會保留相反方向的 2ViewHolder,也就是保留 postion=2,3ViewHolder,並複用 position=1ViewHolder,可是如今 position=0ViewHolderviewType=1,不能夠複用,所以,會繼續往上尋找,這時候就找到了 position=0ViewHolder進行復用。

5、數據更新

5.1 更新方式

當數據源發生變化的時候,咱們通常會經過Adatper. notifyDataSetChanged()來進行界面的刷新,RecyclerView.Adapter也提供了相同的方法:

public final void notifyDataSetChanged() 
複製代碼

除此以外,它還提供了下面幾種方法,讓咱們進行局部的刷新:

//position的數據變化
notifyItemChanged(int postion)
//在position的下方插入了一條數據
notifyItemInserted(int position)
//移除了position的數據
notifyItemRemoved(int postion)
//從position開始,往下n條數據發生了改變
notifyItemRangeChanged(int postion, int n)
//從position開始,插入了n條數據
notifyItemRangeInserted(int position, int n)
//從position開始,移除了n條數據
notifyItemRangeRemoved(int postion, int n)
複製代碼

下面是一些簡單的使用方法:

//在頭部添加多個數據.
   public void addItems() {
        mTitles.add(0, "add Items, name=0");
        mTitles.add(0, "add Items, name=1");
        mNormalAdapter.notifyItemRangeInserted(0, 2);
    }
    //移除頭部的多個數據.
    public void removeItems() {
        mTitles.remove(0);
        mTitles.remove(0);
        mNormalAdapter.notifyItemRangeRemoved(0, 2);
    }
    //移動數據.
    public void moveItems() {
        mTitles.remove(1);
        mTitles.add(2, "move Items name=0");
        mNormalAdapter.notifyItemMoved(1, 2);
    }
複製代碼

5.2 比較

數據的更新分爲兩種:

  • Item changes:除了Item所對應的數據被更新外,沒有其它的變化,對應notifyXXXChanged()方法。
  • Structural changesItems在數據集中被插入、刪除或者移動,對應notifyXXXInsert/Removed/Moved方法。

notifyDataSetChanged會把當前全部的Item和結構都視爲已經失效的,所以它會讓LayoutManager從新綁定Items,並對他們從新佈局,這在咱們知道已經須要更新某個Item的時候,實際上是沒必要要的,這時候就能夠選擇進行局部更新來提升效率。

6、監聽ViewHolder的狀態

RecyclerView.Adapter中還提供了一些回調,讓咱們可以監聽某個ViewHolder的變化:

@Override
    public void onViewRecycled(NormalViewHolder holder) {
        Log.d("NormalAdapter", "onViewRecycled=" + holder);
        super.onViewRecycled(holder);
    }

    @Override
    public void onViewDetachedFromWindow(NormalViewHolder holder) {
        Log.d("NormalAdapter", "onViewDetachedFromWindow=" + holder);
        super.onViewDetachedFromWindow(holder);
    }

    @Override
    public void onViewAttachedToWindow(NormalViewHolder holder) {
        Log.d("NormalAdapter", "onViewAttachedToWindow=" + holder);
        super.onViewAttachedToWindow(holder);
    }
複製代碼

下面,咱們就從實例來說解這幾個方法的調用時機,初始時刻,咱們的界面爲:

  • 初始進入時,position=0~6onViewAttachedToWindow被回調:
  • 當滑動到postion=7可見時,它的onViewAttachedToWindow被回調:
  • postion=0被移出屏幕可視範圍內,它的onViewDetachedFromWindow被回調:
  • 而當咱們繼續往下滑動,當position=2被移出屏幕以後,此時position=0onViewRecycled被回調:
    如今回憶一下以前咱們對複用狀況的分析,RecyclerView最多會保留相反方向上的兩個ViewHolder,此時雖然position=1,2不可見,可是依然須要保留它們,這時候會回收position=0ViewHolder以備以後被複用。

7、監聽RecyclerViewRecyclerView.Adapter的關係

RecyclerViewAdapter是經過setAdapter方法來綁定的,所以在Adapter中也經過了綁定的監聽:

public void onAttachedToRecyclerView(RecyclerView recyclerView) {}
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {}
複製代碼

8、小結

這篇文章,主要總結了一些RecyclerView.Adapter中平時咱們不常注意的細節問題,也經過實例瞭解到了關鍵方法的含義,最後,推薦一個Adapter的開源庫:BaseRecyclerViewAdapterHelper


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

相關文章
相關標籤/搜索