RecyclerView 知識梳理(2) - Adapter

一、概述

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

二、基礎用法

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

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

接著,繼承于RecyclerView.Adapter來實現自定義的NormalAdapter

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時,至少要做四個工作:

  • 第一:繼承于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的操作,這個我們后面再分析。

    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的例子就完成了:

三、只有一種ViewType下的復用情況分析

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

  • onCreateViewHolder
  • onBindViewHolder

通過這兩個方法回調的時機,我們可以對RecyclerView復用的機制有一個大概的了解。

3.1 初始進入

剛開始進入界面的時候,我們只展示了3Item,此時這兩個方法的調用情況如下,可以看到,RecyclerView只實例化了屏幕內可見的ViewHolder,并且onBindViewHolder是在對應的onCreateViewHolder調用完后立即調用的:

3.2 開始滑動

當我們手指觸摸到屏幕,并開始向下滑動,我們會發現,雖然position=3Item還沒有展示出來,但是這時候它的onCreateViewHolderonBindViewHolder就被回調了,也就是說,我們會預加載一個屏幕以外的Item

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.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.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的時候,其實是不必要的,這時候就可以選擇進行局部更新來提高效率。

六、監聽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以備之后被復用。

七、監聽RecyclerViewRecyclerView.Adapter的關系

RecyclerViewAdapter是通過setAdapter方法來綁定的,因此在Adapter中也通過了綁定的監聽:

public void onAttachedToRecyclerView(RecyclerView recyclerView) {}
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {}

八、小結

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


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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容