RecyclerView 之Adapter的簡化過程淺析

前言

前面一篇文章介紹了對于RecyclerView 的擴展和封裝的一個庫,幫助我們在開發中可以快速添加一個列表,提高開發效率。還沒有看過的同學可以在看完本篇文章之后移步前一篇文章RecyclerView Adapter 優雅封裝,一個Adapter搞定所有列表,源碼請看GithubCustomAdapter。本篇文章講的就是Adapter 簡化的思路和過程。

背景

Android 開發中,我們碰到最多的就是列表了,一個APP中有簡單的列表,也有包含很多種Item的復雜列表。大多數的App首頁都是比較復雜的,比如一個社交APP的首頁,包含Banner區、廣告區、文本內容、圖片內容、視頻內容等等。RecyclerView 可以用ViewType 來區分不同的item,也可以滿足需求 ,但還是存在一些問題,比如:1,在item過多邏輯復雜列表界面,Adapter里面的代碼量龐大,邏輯復雜,后期難以維護。2,每次增加一個列表都需要增加一個Adapter,重復搬磚,效率低下。那么本篇文章講的就是從這兩個維度去簡化我們的Adapter。

思路分析與實現過程

上面提出了兩個問題,接下來看一下我們怎么去解決這兩個問題。首先我們來看一下我們常規的寫一個包含多item的復雜的列表界面(比如就是前面說的包含banner,廣告,文本內容,圖片內容,視頻內容的首頁):

/**
 * Created by zhouwei on 17/2/17.
 */

public class HomePageAdapter extends RecyclerView.Adapter {
    public static final int TYPE_BANNER = 0;
    public static final int TYPE_AD = 1;
public static final int TYPE_TEXT = 2;
    public static final int TYPE_IMAGE = 3;
    public static final int TYPE_VIDEO = 4;
    private List<HomePageEntry> mData;

    public void setData(List<HomePageEntry> data) {
        mData = data;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType){
            case TYPE_BANNER:
                return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_banner_layout,null));
            case TYPE_AD:
                return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_ad_item_layout,null));
            case TYPE_TEXT:
                return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_text_item_layout,null));
            case TYPE_IMAGE:
                return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_image_item_layout,null));
            case TYPE_VIDEO:
                return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_video_item_layout,null));
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int type = getItemViewType(position);
        switch (type){
            case TYPE_BANNER:
                // banner 邏輯處理
                break;
            case TYPE_AD:
                // 廣告邏輯處理
                break;
            case TYPE_TEXT:
                // 文本邏輯處理
                break;
            case TYPE_IMAGE:
               //圖片邏輯處理
                break;
            case TYPE_VIDEO:
                //視頻邏輯處理
                break;
            // ... 此處省去N行代碼
        }
    }

    @Override
    public int getItemViewType(int position) {
        if(position == 0){
            return TYPE_BANNER;//banner在開頭
        }else {
            return mData.get(position).type;//type 的值為TYPE_AD,TYPE_IMAGE,TYPE_AD,TYPE_VIDEO其中一個
        }

    }

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



    public static class BannerViewHolder extends RecyclerView.ViewHolder{

        public BannerViewHolder(View itemView) {
            super(itemView);
            //綁定控件
        }
    }

    public static class VideoViewHolder extends RecyclerView.ViewHolder{

        public VideoViewHolder(View itemView) {
            super(itemView);
            //綁定控件
        }
    }
    public static class AdViewHolder extends RecyclerView.ViewHolder{

        public AdViewHolder(View itemView) {
            super(itemView);
            //綁定控件
        }
    }
    public static class TextViewHolder extends RecyclerView.ViewHolder{

        public TextViewHolder(View itemView) {
            super(itemView);
            //綁定控件
        }
    }
    public static class ImageViewHolder extends RecyclerView.ViewHolder{

        public ImageViewHolder(View itemView) {
            super(itemView);
            //綁定控件
        }
    }
}

上面這樣就是我們通常寫一個多Item列表的方法,根據不同的ViewType 處理不同的item,如果邏輯復雜,這個類的代碼量是很龐大的。如果版本迭代添加新的需求,修改代碼很麻煩,后期維護困難。

** 其實可以看到,代碼量在于每種item的視圖綁定、數據綁定和邏輯處理,并且這些操作都是Item共有的操作,既然是共有的,那么我們就可以抽取出來。因此我們可以將Item抽象成為一個獨立的組件(我們將這個組件叫做Cell),它負責每個item的視圖綁定、數據綁定和邏輯處理,現在我們就可以把Adapter 中的代碼放到Cell 中去了。Adapter 綁定視圖和綁定數據的操作調用對應位置的Cell的方法就可以了。**

那我們來看一下Cell,Cell作為一個獨立組件,它有以下功能:
1,視圖綁定
2,數據綁定
3,資源釋放
4,獨立id(讓Adapter 區分是那種Cell)

1,首先我們定義一個頂層接口Cell,對應上面的4個功能,有4個方法

/**
 * Created by zhouwei on 17/1/19.
 */

public interface Cell {
    /**
     * 回收資源
     *
     */
    public void releaseResource();

    /**
     * 獲取viewType
     * @return
     */
    public  int getItemType();

    /**
     * 創建ViewHolder
     * @param parent
     * @param viewType
     * @return
     */
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);

    /**
     * 數據綁定
     * @param holder
     * @param position
     */
    public  void onBindViewHolder(RecyclerView.ViewHolder holder, int position);
}

2,既然可以綁定數據,我們要給它數據源,因此定義一個基類RVBaseCell,保存一個范型數據實體

public  abstract class RVBaseCell<T> implements Cell {

    public RVBaseCell(T t){
        mData = t;
    }
    public T mData;

    @Override
    public void releaseResource() {
        // do nothing
        // 如果有需要回收的資源,子類自己實現
    }
}

**3,將原來Adapter 中的視圖綁定、數據綁定、邏輯處理等操作,放到對應的Cell 中去做 **
例如,我們為Banner創建一個Cell ,叫BannerCell。代碼如下:

/**
 * Created by zhouwei on 17/2/17.
 */

public class BannerCell extends RVBaseCell<HomePageEntry>{
    public static final int TYPE_BANNER = 0;
    public BannerCell(HomePageEntry homePageEntry) {
        super(homePageEntry);
    }

    @Override
    public int getItemType() {
        return TYPE_BANNER;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_banner_layout,null));
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        //處理banner 邏輯
    }

    public static class BannerViewHolder extends RecyclerView.ViewHolder{

        public BannerViewHolder(View itemView) {
            super(itemView);
            //綁定控件
        }
    }
}

** 4,改造Adapter,Adapter中保存的不再是實體數據,而是一個Cell列表,改造Adapter 回調方法,調用對應位置的Cell中的方法。**
代碼如下:


public  class HomePageAdapter<C extends RVBaseCell> extends RecyclerView.Adapter {
    private List<C> mData;

    public void setData(List<C> data) {
        mData = data;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        for(int i=0;i<getItemCount();i++){
            if(viewType == mData.get(i).getItemType()){
                return mData.get(i).onCreateViewHolder(parent,viewType);
            }
        }

        throw new RuntimeException("wrong viewType");
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

    }

    @Override
    public int getItemViewType(int position) {
        return mData.get(position).getItemType();

    }

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

現在看一下這個HomePageAdapter 的代碼和之前寫的 HomePageAdapter的代碼,可以把一個上千行代碼的Adapter 縮減為只有幾十行代碼,并且更重要的是,這個Adapter 是一個通用的,我們所有的列表只需要這么一個Adapter 就OK了,只需要添加Cell就行。這樣就把我們文章開始提到的兩個問題都解決了。

我們可以用兩個圖來對比一下,以前添加一個列表就要寫一個Adapter:

old_adapter.png

現在是這樣的:

new_adapter.png

如上圖所示,只需要一個Adapter就行, 就像是Adapter上面有很多插槽,我們將一個個Cell插到Adapter上,即插即用。完全解耦,以后添加需求和砍掉需求,只需要增加一種Cell 或者減少一種Cell就行,不用動以前的老代碼,維護方便。

最后

以上就是RecyclerView Adapter 的簡化封裝過程與思路分析,文章中的代碼只放了部分,詳細的封裝代碼請看GithubCustomAdapter。CustomAdapter也集成了更多的功能,歡迎start。

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

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,646評論 25 708
  • 內容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進度條TabLayout圖標下拉刷新...
    皇小弟閱讀 46,845評論 22 665
  • 簡介: 提供一個讓有限的窗口變成一個大數據集的靈活視圖。 術語表: Adapter:RecyclerView的子類...
    酷泡泡閱讀 5,199評論 0 16
  • 序列化就是可以把對象存儲在本地文件,把數據可以在網絡中傳輸,也可以在IPC機制中傳遞。android中可以使用二種...
    金館長說閱讀 293評論 0 0
  • 外婆家的小村莊 有時夢里會回到這個地方,那真是一個偏遠的小村莊,就在益陽、常德和懷化三市交界之地,不管從哪個城市來...
    下鋪兄弟閱讀 2,001評論 0 51