RecyclerView多功能集合適配器:SuperAdapter

引言

從上學(xué)到工作,一晃搞Android也已經(jīng)有幾年了,用的最多控件不外乎就那么幾個竹椒,其中列表控件用起來相對來說比較繁瑣朦促,尤其是出了RecyclerView之后膝晾。前段時間突發(fā)奇想做一個通用的適配器這樣就不用每次寫重復(fù)的東西了那多爽啊务冕!這里還要好好感謝一下網(wǎng)上那些技術(shù)博客大神們血当,很多東西都是借鑒他們的思想。

為什么只封裝適配器呢?
雖說把RecyclerView一起封裝就能做出更多好用的功能歹颓,用戶體驗也會更好坯屿,但可能是歷史遺留陰影,以前用別人的開源控件時總?cè)菀壮鳇c(diǎn)問題∥】福現(xiàn)在這樣就是一個單純的適配器领跛,不用考慮列表的布局是線性的還是網(wǎng)格的等類似問題。與界面無關(guān)撤奸,只干干凈凈的定義了邏輯規(guī)則吠昭,這種感覺太爽了~

最后,GitHub 上項目地址傳送門:SuperAdapter

SuperAdapter是什么

SuperAdapter 是對RecyclerView.Adapter進(jìn)行封裝并將許多常用功能集成之中的一個Android庫胧瓜。你不需要為每個列表單獨(dú)去寫ViewHolder后再聲明控件矢棚,當(dāng)構(gòu)建不需要重用的列表時你甚至不需要單獨(dú)創(chuàng)建類去繼承RecyclerView.Adapter定制適配器,只要在ActivityFragment中簡單寫下幾行代碼即可實(shí)現(xiàn)府喳,有效的減化了復(fù)寫代碼的數(shù)量蒲肋。

SuperAdapter目前有哪些功能

  • 快速綁定單一布局列表
  • 簡化多類型布局列表
  • 列表點(diǎn)擊事件
  • 分頁顯示數(shù)據(jù)
  • 自定義列表唯一頂部Header
  • 自定義列表底部Footer
  • 上拉自動加載更多數(shù)據(jù)
  • 添加空數(shù)據(jù)提示視圖

下載SuperAdapter

你需要在項目的根 build.gradle 加入如下JitPack倉庫鏈接:

allprojects {
    repositories {
        ...     
        maven { url 'https://jitpack.io' }      
    }
}

著在你的需要依賴的Module的build.gradle加入依賴:

compile 'com.github.JesseWuuu:SuperAdapter:x.y.z'

注意:上面依賴中的 x.y.z 為版本號,目前最新的版本號為 -> 0.2.0

關(guān)于開源

如果你只是想了解如何使用它可以跳過本節(jié)钝满。

在大學(xué)期間懵懵懂懂的聽老師說了一大堆開源的意義兜粘,雖然現(xiàn)在也還是不太能領(lǐng)會其中的精神,但是這幾年一路走來看過不少大神們的開源代碼弯蚜,也算是在其中獲得不少的好處孔轴。

雖說這個工具只能算是個小玩具級別,但我還是想將它分享給志同道合的朋友們碎捺,源碼中核心部分我都認(rèn)真的寫全了注釋路鹰,希望能有同樣對這個小工具感興趣的朋友和我一起來繼續(xù)完善它,要是能 Star 一下項目就更好啦哈哈哈~

接下來我將繼續(xù)為這個小工具添加新的實(shí)用功能收厨,當(dāng)然如果使用過程中有問題可以添加到 Issues 中晋柱,我會經(jīng)常看的诵叁。

GitHub地址傳送門:SuperAdapter

定義與聲明

如果你的列表只出現(xiàn)在了一個布局中并不會重復(fù)用使用趣斤,那么使用SuperAdapter時就不需要您單獨(dú)創(chuàng)建類繼承父類來定制化適配器,但是還是需要遵守一項規(guī)則:在ActivityFragment中聲明SuperAdapter屬性的同時需要聲明您的列表數(shù)據(jù)源類型:

List<DataEntity> mData;
SuperAdapter<DataEntity> mAdapter; 

如果你的列表會在多處重復(fù)使用黎休,這時你需要將 SuperAdapter 封裝成自定義的 Adapter浓领,下面會有專門一節(jié)講封裝 SuperAdapter 需要注意的事項,此處先不提势腮。

綁定單一布局的列表

綁定單一布局列表的方法非常簡單联贩,只需要在SuperAdapter的構(gòu)造函數(shù)中添加布局文件的layoutId與數(shù)據(jù)源即可。

SuperAdapter<DataEntity> adapter = new SuperAdapter<DataEntity>(R.layout.view_list_item_1){

    @Override
    public void bindView(ViewHolder itemView, DataEntity data,int position) {
        // 此處寫綁定itemview中的控件邏輯
        itemView.<TextView>getView(R.id.text_1).setText(data.getTitle());
        Button button = holder.<Button>getView(R.id.button);
        button.setEnabled(entity.IsEnabled());   
    }               
};

我們需要在方法 void bindView(ViewHolder itemView, DataEntity data,int position) 中綁定列表每個條目中的控件并進(jìn)行邏輯處理捎拯。其中方法參數(shù)ViewHolder itemView 為自定義 ViewHolder泪幌。

通用ViewHolder

為方便管理,SuperAdapter 中持有的 ViewHolder 均為一個通用的自定義 ViewHolder ,其中它對外提供了一個 <T extends View>T getView(int viewId) 方法來代替 View findViewById(int ViewId)獲取布局中的控件祸泪,目的是簡化綁定控件方法以及對每個條目中的子 view 做了簡單的緩存,具體使用方法上述代碼段中有體現(xiàn)吗浩。

綁定多種布局的列表

綁定多種類型布局文件需要構(gòu)造器MultiItemViewBuilder<T>來輔助適配器確定每個位置調(diào)用對應(yīng)的布局文件。同理没隘,聲明屬性同時需要聲明數(shù)據(jù)源類型:

MultiItemViewBuilder<DataEntity> multiItemViewBuilder;

MultiItemViewBuilder需要您實(shí)現(xiàn)兩個方法:

  • int getItemType(int position,T data)懂扼,通過參數(shù)中的位置和數(shù)據(jù)源來判斷返回ItemView類型,ItemView類型的值需自定義右蒲。
  • int getLayoutId(int type)阀湿,通過判斷itemType返回對應(yīng)的布局文件id。
MultiItemViewBuilder<DataEntity> multiItemViewBuilder = MultiItemViewBuilder<TestEntity>() {
        @Override
        public int getLayoutId(int type) {
            if (type == 0){
                return R.layout.view_item_normal;
            }
            return R.layout.view_item_other;
        }

        @Override
        public int getItemType(int position, DataEntity data) {
                
            if(entity.isTest()){
                return 0;
            }else{
                return 1;
            }

        }
 };

最后瑰妄,將MultiItemViewBuilder直接添加到SuperAdapter構(gòu)造函數(shù)中即可:

mAdapter = new SuperAdapter<DataEntity>(multiItemViewBuilder,mData) {

        @Override
        public void convert(ViewHolder holder, DataEntity entity) {
              // 此處寫綁定itemview中的控件邏輯
        }

 };

最后在方法convert()中綁定控件邏輯時需要針對不同的控件類型分別處理陷嘴。

綁定列表點(diǎn)擊事件

綁定點(diǎn)擊事件:

mAdapter.setOnItemClickListener(new SuperAdapter.OnItemClickListener<DataEntity>() {
        @Override
        public void onItemClick(int position, DataEntity entity) {
            // 此處寫點(diǎn)擊事件的邏輯
        }
});

綁定長按事件:

mAdapter.setOnItemLongClickListener(new SuperAdapter.OnItemLongClickListener<DataEntity>() {
        @Override
        public void onItemLongClick(int position, DataEntity entity) {
            // 此處寫長按事件的邏輯
        }
});

設(shè)置空數(shù)據(jù)視圖

空數(shù)據(jù)視圖是指列表的數(shù)據(jù)源數(shù)據(jù)為空時顯示的提示視圖。設(shè)置的使用方法非常簡單:

mAdapter.setEmptyDataView(R.layout.empty_view);

在為列表添加了 Header 與 Footer 后即使數(shù)據(jù)源為空也不認(rèn)為需要顯示空視圖

添加列表唯一頂部Header

為什么要說是列表“唯一頂部”的 Header 呢间坐?因為除該 Header 外灾挨,還有一種用于顯示分類信息的 Header ,類似與通訊錄中通過首字母 A~Z 順序顯示聯(lián)系人竹宋。

自定義頭部控件的本質(zhì)其實(shí)就是列表中的一個特殊ItemView劳澄,它永遠(yuǎn)存在列表最上方的位置且跟隨列表滑動。

添加自定義頭部需要實(shí)現(xiàn)一個頭部構(gòu)造器來管理自定義頭部布局:

HeaderBuilder builder = new HeaderBuilder() {
        @Override
        public int getHeaderLayoutId() {
            return R.layout.view_header;
        }

        @Override
        public void bindHeaderView(ViewHolder holder) {
            ImageView background = holder.getView(R.id.header_img);
            holder.<TextView>getView(R.id.header_name).setText("Test UserName);
        }

};

構(gòu)造器接口中有兩個回調(diào)方法:

  • int getLayoutId() 設(shè)置自定義頭部的布局id逝撬;
  • void convert(ViewHolder view) 綁定布局中的控件及添加邏輯浴骂;

最后乓土,將實(shí)現(xiàn)好的頭部構(gòu)造器添加到適配器中即可:

mAdapter.addHeader(builder);

添加列表分類Header

開發(fā)中宪潮。。趣苏。

這個功能有點(diǎn)費(fèi)勁啊狡相。。食磕。

添加列表底部Footer

Footer 是為滿足特殊需求一直存在于列表底部的ItemView尽棕,通常情況下是配合列表分頁加載數(shù)據(jù)使用。添加 Footer 的方法與之前添加 Header 的方法類似彬伦,需要一個構(gòu)造器管理:

FooterBuilder builder = new FooterBuilder() {
        
       /**
        * 獲取Footer的布局文件Id
        */        
        @Override
        public int getFooterLayoutId() {
            return R.layout.view_footer;
        }

       /**
        * 非用于分頁加載更多數(shù)據(jù)狀態(tài)下Footer的界面邏輯處理
        */
        @Override
        public void onNormal(ViewHolder holder) {
            holder.<ProgressBar>getView(R.id.footer_progress).setVisibility(View.GONE);
            holder.<TextView>getView(R.id.footer_msg).setText("這是個底部");
        }

       /**
        * 分頁加載更多數(shù)據(jù) - “正在加載數(shù)據(jù)中” 狀態(tài)的界面邏輯處理
        */
        @Override
        public void onLoading(ViewHolder holder) {
            holder.<ProgressBar>getView(R.id.footer_progress).setVisibility(View.VISIBLE);
            holder.<TextView>getView(R.id.footer_msg).setText("正在加載數(shù)據(jù)中");
        }

       /**
        * 分頁加載更多數(shù)據(jù) - “加載數(shù)據(jù)失敗” 狀態(tài)的界面邏輯處理
        *
        * @param msg 數(shù)據(jù)加載失敗的原因
        */
        @Override
        public void onLoadingFailure(ViewHolder holder, String msg) {
            holder.<ProgressBar>getView(R.id.footer_progress).setVisibility(View.GONE);
            holder.<TextView>getView(R.id.footer_msg).setText(msg);
        }

       /**
        * 分頁加載更多數(shù)據(jù) - “沒有更多數(shù)據(jù)” 狀態(tài)的界面邏輯處理
        */
        @Override
        public void onNoMoreData(ViewHolder holder) {
            holder.<ProgressBar>getView(R.id.footer_progress).setVisibility(View.GONE);
            holder.<TextView>getView(R.id.footer_msg).setText("已經(jīng)到底啦");
        }
};

因為 Footer 需要經(jīng)常配合列表分頁加載數(shù)據(jù)使用滔悉,所以在構(gòu)造器中除了正常使用情況下的方法onNormal(ViewHolder holder)外還提供了三個用于管理分頁加載數(shù)據(jù)狀態(tài)的方法:

  • onLoading(ViewHolder holder) 正在加載數(shù)據(jù)中
  • onLoadingFailure(ViewHolder holder, String msg) 數(shù)據(jù)加載失敗,msg為失敗的原因
  • onNoMoreData(ViewHolder holder) 已經(jīng)加載完所有數(shù)據(jù)

最后单绑,將構(gòu)造器添加到 SuperAdapter 中:

mAdapter.addFooter(builder);

簡易Footer構(gòu)造器SimpleFooterBuilder

考慮到構(gòu)造 Footer 的過程有些繁瑣, SuperAdapter 庫中提供了一個簡易的內(nèi)部定義好的 Footer 構(gòu)造器 SimpleFooterBuilder,使用方法很簡單:

mAdapter.addFooter(new SimpleFooterBuilder("這是個底部","正在加載數(shù)據(jù)中","加載數(shù)據(jù)失敗","已經(jīng)到底啦"));

SimpleFooterBuilder構(gòu)造方法中的4個參數(shù)依次對應(yīng)著 Footer 的正常模式回官、正在加載、加載失敗搂橙、加載完畢這幾種狀態(tài)的提示信息歉提。

分頁自動加載更多數(shù)據(jù)

注意!在設(shè)置分頁加載數(shù)據(jù)前一定要先添加Footer

在使用該功能時,用戶可以自己設(shè)置分頁請求數(shù)據(jù)的起始頁苔巨,每當(dāng)列表滑動到底部的時候就會按之前設(shè)置的頁數(shù)依次累加請求新的數(shù)據(jù)版扩。

設(shè)置分頁加載數(shù)據(jù)調(diào)用 SuperAdapter 的setPaginationData()方法就可以,但是在這之前需要一個加載數(shù)據(jù)監(jiān)聽器LoadDataListener來管理數(shù)據(jù)的加載狀態(tài)侄泽,當(dāng)列表需要加載新的數(shù)據(jù)時就會調(diào)用監(jiān)聽器的onLoadingData()方法礁芦,該方法有兩個參數(shù):

  • int loadPage 需要加載新數(shù)據(jù)的頁數(shù)
  • LoadDataStatus loadDataStatus 分頁加載數(shù)據(jù)的狀態(tài)控制器

分頁加載數(shù)據(jù)的狀態(tài)控制器LoadDataStatus 提供了三種狀態(tài):

  • onSuccess(List<T> datas) 數(shù)據(jù)請求成功調(diào)用該方法傳入新數(shù)據(jù)
  • onFailure(String msg) 數(shù)據(jù)請求失敗調(diào)用該方法傳入失敗信息
  • onNoMoreData() 數(shù)據(jù)全部加載完畢調(diào)用該方法

注意!加載數(shù)據(jù)一定要調(diào)用狀態(tài)控制器LoadDataStatus中的方法給列表加載狀態(tài)做反饋

具體的實(shí)現(xiàn)代碼:

// 分頁加載數(shù)據(jù)的起始頁
private static final int START_PAGE = 0;

...
...

// 實(shí)現(xiàn)加載數(shù)據(jù)監(jiān)聽器
LoadDataListener listener = new LoadDataListener() {
    
        @Override
        public void onLoadingData(final int loadPage, final LoadDataStatus loadDataStatus) {
            
                DataManager.getListPaginationData(loadPage,new Callback(){
                        
                        @Override
                        public void onSuccess(List<String> datas){
                            if(datas.size() == 0){
                                loadDataStatus.onNoMoreData();
                            }else{
                                loadDataStatus.onSuccess(datas);
                            }
                        }

                        @Override
                        public void onFailure(String msg){
                            loadDataStatus.onFailure(msg);
                        }

                        @Override
                        public void onError(){
                            loadDataStatus.onFailure("網(wǎng)絡(luò)請求出錯");
                        }                                
                });
        }
}

// 設(shè)置分頁加載數(shù)據(jù)
mAdapter.setPaginationData(START_PAGE, listener);

封裝 SuperAdapter 的注意事項

SuperAdaper類中提供了兩個有參構(gòu)造方法:

  • SuperAdapter(int layoutId )
  • SuperAdapter(MultiItemViewBuilder multiItemViewBuilder)

他們實(shí)現(xiàn)了不同類型的子布局蔬顾,所以繼承SuperAdapter時要Super()其中一個構(gòu)造方法宴偿,但是你肯定不希望每次實(shí)例化適配器都要定義它們,所以建議在你的自定義類中將布局類型以static方法定義好:

private static int layoutId = R.layout.view_list_item;

或者

private static MultiItemViewBuilder<TestEntity> multiItemViewBuilder = new MultiItemViewBuilder<TestEntity>() {

        @Override
        public int getLayoutId(int type) {
            if (type == 0){
                return R.layout.view_list_item_1;
            }else {
                return R.layout.view_list_item_2;
            }
        }

        @Override
        public int getItemType(int position, TestEntity data) {
            return data.getType();
        }
    };

這樣自定義類的構(gòu)造方法中就無需填寫參數(shù):

public MineAdapter() {
    super(layoutId);
    // do something
}

或者

public MineAdapter() {
    super(multiItemViewBuilder);
    // do something
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诀豁,一起剝皮案震驚了整個濱河市窄刘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舷胜,老刑警劉巖娩践,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異烹骨,居然都是意外死亡翻伺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門沮焕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吨岭,“玉大人,你說我怎么就攤上這事峦树±北瑁” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵魁巩,是天一觀的道長急灭。 經(jīng)常有香客問我,道長谷遂,這世上最難降的妖魔是什么葬馋? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮肾扰,結(jié)果婚禮上畴嘶,老公的妹妹穿的比我還像新娘。我一直安慰自己集晚,他們只是感情好窗悯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著甩恼,像睡著了一般蟀瞧。 火紅的嫁衣襯著肌膚如雪沉颂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天悦污,我揣著相機(jī)與錄音铸屉,去河邊找鬼。 笑死切端,一個胖子當(dāng)著我的面吹牛彻坛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播踏枣,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼昌屉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了茵瀑?” 一聲冷哼從身側(cè)響起间驮,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎马昨,沒想到半個月后竞帽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸿捧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年屹篓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匙奴。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡堆巧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泼菌,到底是詐尸還是另有隱情谍肤,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布灶轰,位于F島的核電站谣沸,受9級特大地震影響刷钢,放射性物質(zhì)發(fā)生泄漏笋颤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一内地、第九天 我趴在偏房一處隱蔽的房頂上張望伴澄。 院中可真熱鬧,春花似錦阱缓、人聲如沸非凌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽敞嗡。三九已至颁糟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間喉悴,已是汗流浹背棱貌。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留箕肃,地道東北人婚脱。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像勺像,于是被迫代替她去往敵國和親障贸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,072評論 25 707
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案吟宦? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 13,748評論 1 92
  • 近期一直在一個自憐的悲觀思緒中踐踏自己的自信篮洁,想想目前的生活過得不如意都是由于自己的決策失誤導(dǎo)致。一個完全交學(xué)費(fèi)...
    墨道尋常閱讀 295評論 0 0
  • 火山 經(jīng)歷雨水沖激三十二年 某個深夜 一匹流浪的狼 選擇跳進(jìn)巖漿 怒目圓睜殃姓, 咬著冷冷的牙 一瞬間 巖漿被撞破了缺...
    關(guān)馨仁閱讀 329評論 0 2
  • 我嘀粱,一個簡單普通的人。女性辰狡、職員锋叨、媽媽。 大學(xué)畢業(yè)后就職在湖南長沙某知名互聯(lián)網(wǎng)公司(上市企業(yè)宛篇,2000人及以上)娃磺,...
    槑的陳閱讀 891評論 0 3