一個(gè)易維護(hù)的 RecyclerView.Adapter 的實(shí)現(xiàn)

使用 RecyclerView 實(shí)現(xiàn)下面的效果:

  1. 有兩種布局缰贝,紅色布局和藍(lán)色布局
  2. 偶數(shù)位置的是紅色布局孔庭,奇數(shù)位置的是藍(lán)色布局

按照一般的方法來實(shí)現(xiàn) Adapter 的話茁彭,代碼如下

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int TYPE_RED = 0;
    private static final int TYPE_BLUE = 1;

    private List<String> mDataSource;

    public MyAdapter(List<String> dataSource) {
        mDataSource = dataSource;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_RED) {
            return new RedViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_red, parent, false));
        } else {
            return new BlueViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_blue, parent, false));
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder.getItemViewType() == TYPE_RED) {
            RedViewHolder redViewHolder = (RedViewHolder) holder;
            redViewHolder.tv.setText(mDataSource.get(position));
        } else {
            BlueViewHolder blueViewHolder = (BlueViewHolder) holder;
            blueViewHolder.tv.setText(mDataSource.get(position));
        }
    }

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

    @Override
    public int getItemViewType(int position) {
        if (position % 2 == 0) {
            return TYPE_RED;
        } else {
            return TYPE_BLUE;
        }
    }

    private static class RedViewHolder extends RecyclerView.ViewHolder {

        TextView tv;

        RedViewHolder(View itemView) {
            super(itemView);
            tv = (TextView) itemView.findViewById(R.id.tv);
        }
    }

    private static class BlueViewHolder extends RecyclerView.ViewHolder {

        TextView tv;

        BlueViewHolder(View itemView) {
            super(itemView);
            tv = (TextView) itemView.findViewById(R.id.tv);
        }
    }
}

因?yàn)橛卸喾N布局熬丧,所以重點(diǎn)關(guān)注的是如何判斷在某一個(gè)位置要如何展示哪一個(gè)布局糕簿,RecyclerView.Adapter 提供了 int getItemViewType(int position) 方法讓我們重寫舵鳞,給每一個(gè)布局指定一個(gè)唯一標(biāo)識(shí) ItemViewType

按照效果圖:偶數(shù)位置的是紅色布局震檩,奇數(shù)位置的是藍(lán)色布局,所以方法重寫如下

// 布局的唯一標(biāo)識(shí)
private static final int TYPE_RED = 0;
private static final int TYPE_BLUE = 1;

// 布局的判斷
@Override
public int getItemViewType(int position) {
    if (position % 2 == 0) {
        return TYPE_RED;
    } else {
        return TYPE_BLUE;
    }
}

有了布局的唯一標(biāo)識(shí) ItemViewType 之后蜓堕,就可以在
RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
void onBindViewHolder(RecyclerView.ViewHolder holder, int position) 中根據(jù)這個(gè)唯一標(biāo)識(shí)抛虏,創(chuàng)建和綁定不同的 ViewHolder 了。

缺陷

這個(gè)寫法沒什么毛病套才,但是在維護(hù)上會(huì)有一些問題嘉蕾。如果將來要增加一個(gè)新的布局,假設(shè)位置的尾數(shù)逢 5 的地方需要顯示綠色布局霜旧,就需要改動(dòng)幾個(gè)地方

  1. 增加或布局的唯一標(biāo)識(shí)错忱,也就是改動(dòng)這個(gè)地方
private static final int TYPE_RED = 0;
private static final int TYPE_BLUE = 1;
private static final int TYPE_GREEN = 2;  // 增加一個(gè)標(biāo)識(shí)
  1. 增加一個(gè) ViewHolder
private static class GreenViewHolder extends RecyclerView.ViewHolder {

    TextView tv;

    GreenViewHolder(View itemView) {
        super(itemView);
        tv = (TextView) itemView.findViewById(R.id.tv);
    }
}
  1. 修改 int getItemViewType(int position) 的 if-else 判斷
@Override
public int getItemViewType(int position) {
    // 這里的 if - else 判斷需要修改
    if (position % 2 == 0) {
        return TYPE_RED;
    } else if (position % 5 == 0){
      return TYPE_GREEN;  
    } else {
      return TYPE_BLUE;
    }
}
  1. 修改 RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 方法儡率,增加一個(gè) ViewHolder
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == TYPE_RED) {
        return new RedViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_red, parent, false));
    } else if (viewType == TYPE_GREEN){
        // 增加一個(gè) GreenViewHolder
        return new GreenViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_green, parent, false));
    } else {
        return new BlueViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_blue, parent, false));
    }
}
  1. 修改 void onBindViewHolder(RecyclerView.ViewHolder holder, int position) 方法,處理 GreenViewHolder 的邏輯
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (holder.getItemViewType() == TYPE_RED) {
        RedViewHolder redViewHolder = (RedViewHolder) holder;
        redViewHolder.tv.setText(mDataSource.get(position));
    } else if (holder.getItemViewType() == TYPE_GREEN){
        // 處理 GreenViewHolder 的邏輯
        GreenViewHolder greenViewHolder = (GreenViewHolder) holder;
        greenViewHolder.tv.setText(mDataSource.get(position));
    } else {
        BlueViewHolder blueViewHolder = (BlueViewHolder) holder;
        blueViewHolder.tv.setText(mDataSource.get(position));
    }
}

發(fā)現(xiàn)問題

可以看到增加一個(gè)布局以清,需要改動(dòng)很多地方儿普。問題的根源就在于 if-else 判斷這一塊代碼。如果讓一個(gè)類來幫我們?cè)?int getItemViewType(int position) 方法中返回唯一標(biāo)識(shí)掷倔,同時(shí)在 RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
void onBindViewHolder(RecyclerView.ViewHolder holder, int position) 方法中幫我們根據(jù) itemViewType 拿到 ViewHolder眉孩,這樣就能大幅度地減少代碼。

實(shí)現(xiàn)

可以看到 itemViewType 和 ViewHolder 是一一對(duì)應(yīng)的關(guān)系勒葱,那么就可以使用 HashMap<Integer, RecyclerView.ViewHolder> 來存放 itemViewType 和 ViewHolder浪汪。

接著再定義一個(gè)接口

public interface AdapterDelegate<T> {

    /**
     * 確定唯一標(biāo)識(shí)
     */
    boolean isForViewType(List<T> items, int position);

    /**
     * 創(chuàng)建 ViewHolder
     */
    RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);

    /**
     * 綁定 ViewHolder
     */
    void onBindViewHolder(List<T> items, int position, RecyclerView.ViewHolder holder);
}

把確定唯一標(biāo)識(shí)、創(chuàng)建 ViewHolder 和 綁定 ViewHolder 的工作交給實(shí)現(xiàn)接口的具體的 ViewHolder 來實(shí)現(xiàn)凛虽。

最終效果

MyAdapter 現(xiàn)在只有這些代碼

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private AdapterDelegatesManager<String> mAdapterDelegatesManager;

    private List<String> mDataSource;

    public MyAdapter(List<String> dataSource) {
        mDataSource = dataSource;
        mAdapterDelegatesManager = new AdapterDelegatesManager<>();
        mAdapterDelegatesManager  // 在這里綁定 ViewHolder
                .addDelegate(new RedViewHolder())
                .addDelegate(new GreenViewHolder())
                .addDelegate(new BlueViewHolder());
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return mAdapterDelegatesManager.onCreateViewHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        mAdapterDelegatesManager.onBindViewHolder(mDataSource, position, holder);
    }

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

    @Override
    public int getItemViewType(int position) {
        return mAdapterDelegatesManager.getItemViewType(mDataSource, position);
    }
}

剩下的就是定義 ViewHolder 實(shí)現(xiàn) AdapterDelegate<T> 接口就行

/**
 * RedViewHolder
 */
private static class RedViewHolder implements AdapterDelegate<String> {

    @Override
    public boolean isForViewType(List<String> items, int position) {
        return position % 2 == 0;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
        return new ViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_red, parent, false));
    }

    @Override
    public void onBindViewHolder(List<String> items, int position, RecyclerView.ViewHolder holder) {
        ViewHolder redViewHolder = (ViewHolder) holder;
        redViewHolder.tv.setText(items.get(position));
    }

    private class ViewHolder extends RecyclerView.ViewHolder {

        TextView tv;

        ViewHolder(View itemView) {
            super(itemView);
            tv = (TextView) itemView.findViewById(R.id.tv);
        }
    }
}


/**
 * BlueViewHolder
 */
private static class BlueViewHolder implements AdapterDelegate<String> {

    @Override
    public boolean isForViewType(List<String> items, int position) {
        return position % 2 != 0;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
        return new ViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_blue, parent, false));
    }

    @Override
    public void onBindViewHolder(List<String> items, int position, RecyclerView.ViewHolder holder) {
        ViewHolder viewHolder = (ViewHolder) holder;
        viewHolder.tv.setText(items.get(position));
    }

    private class ViewHolder extends RecyclerView.ViewHolder {

        TextView tv;

        ViewHolder(View itemView) {
            super(itemView);
            tv = (TextView) itemView.findViewById(R.id.tv);
        }
    }
}


/**
 * GreenViewHolder
 */
private static class GreenViewHolder implements AdapterDelegate<String> {

    @Override
    public boolean isForViewType(List<String> items, int position) {
        return position % 5 == 0;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
        return new ViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_green, parent, false));
    }

    @Override
    public void onBindViewHolder(List<String> items, int position, RecyclerView.ViewHolder holder) {
        ViewHolder viewHolder = (ViewHolder) holder;
        viewHolder.tv.setText(items.get(position));
    }

    private class ViewHolder extends RecyclerView.ViewHolder {

        TextView tv;

        ViewHolder(View itemView) {
            super(itemView);
            tv = (TextView) itemView.findViewById(R.id.tv);
        }
    }
}

核心代碼的實(shí)現(xiàn)

這個(gè)類是按照 JOE'S GREAT ADAPTER HELL ESCAPE 介紹的思想來實(shí)現(xiàn)的死遭,核心指出就在于 itemViewType 和 ViewHolder 是一一對(duì)應(yīng)的關(guān)系

AdapterDelegate 接口

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

import java.util.List;

public interface AdapterDelegate<T> {

    boolean isForViewType(List<T> items, int position);

    RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);

    void onBindViewHolder(List<T> items, int position, RecyclerView.ViewHolder holder);
}

AdapterDelegatesManager 的實(shí)現(xiàn)

import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.ViewGroup;

import java.util.List;

public class AdapterDelegatesManager<T> {

    /**
     * Key: ItemViewType
     * Value: AdapterDelegate
     */
    private SparseArray<AdapterDelegate<T>> mDelegates = new SparseArray<>();

    public AdapterDelegatesManager<T> addDelegate(AdapterDelegate<T> delegate) {
        if (delegate == null) {
            throw new NullPointerException("AdapterDelegate of item can not be null");
        } else {
            int newItemViewType = mDelegates.size();
            mDelegates.put(newItemViewType, delegate);
        }
        return this;
    }

    public int getItemViewType(List<T> items, int position) {
        int delegateCount = mDelegates.size();
        for (int itemViewType = 0; itemViewType < delegateCount; itemViewType++) {
            AdapterDelegate<T> delegate = mDelegates.valueAt(itemViewType);
            if (delegate.isForViewType(items, position)) {
                return itemViewType;
            }
        }
        throw new IllegalArgumentException(
                "No AdapterDelegate added that matches position=" + position + " in data source");
    }

    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        AdapterDelegate delegate = mDelegates.get(viewType);
        if (delegate == null) {
            throw new NullPointerException("No AdapterDelegate added for ViewType " + viewType);
        } else {
            return delegate.onCreateViewHolder(parent);
        }
    }

    public void onBindViewHolder(List<T> items, int position, RecyclerView.ViewHolder viewHolder) {
        AdapterDelegate<T> delegate = mDelegates.get(viewHolder.getItemViewType());
        if (delegate == null) {
            throw new NullPointerException(
                    "No AdapterDelegate added for ViewType " + viewHolder.getItemViewType());
        } else {
            delegate.onBindViewHolder(items, position, viewHolder);
        }
    }
}

參考來源

http://hannesdorfmann.com/android/adapter-delegates
https://github.com/sockeqwe/AdapterDelegates

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凯旋,一起剝皮案震驚了整個(gè)濱河市呀潭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌至非,老刑警劉巖钠署,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異荒椭,居然都是意外死亡谐鼎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門趣惠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來该面,“玉大人,你說我怎么就攤上這事信卡「糇海” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵傍菇,是天一觀的道長(zhǎng)猾瘸。 經(jīng)常有香客問我,道長(zhǎng)丢习,這世上最難降的妖魔是什么牵触? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮咐低,結(jié)果婚禮上揽思,老公的妹妹穿的比我還像新娘。我一直安慰自己见擦,他們只是感情好钉汗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布羹令。 她就那樣靜靜地躺著,像睡著了一般损痰。 火紅的嫁衣襯著肌膚如雪福侈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天卢未,我揣著相機(jī)與錄音肪凛,去河邊找鬼。 笑死辽社,一個(gè)胖子當(dāng)著我的面吹牛伟墙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滴铅,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼戳葵,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了失息?” 一聲冷哼從身側(cè)響起譬淳,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤档址,失蹤者是張志新(化名)和其女友劉穎盹兢,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體守伸,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绎秒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尼摹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片见芹。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蠢涝,靈堂內(nèi)的尸體忽然破棺而出玄呛,到底是詐尸還是另有隱情,我是刑警寧澤和二,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布徘铝,位于F島的核電站,受9級(jí)特大地震影響惯吕,放射性物質(zhì)發(fā)生泄漏惕它。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一废登、第九天 我趴在偏房一處隱蔽的房頂上張望淹魄。 院中可真熱鬧,春花似錦堡距、人聲如沸甲锡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搔体。三九已至恨樟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疚俱,已是汗流浹背劝术。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呆奕,地道東北人养晋。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像梁钾,于是被迫代替她去往敵國和親绳泉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容