RecyclerView多功能集合適配器:SuperAdapter

引言

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

為什么只封裝適配器呢?
雖說把RecyclerView一起封裝就能做出更多好用的功能,用戶體驗也會更好,但可能是歷史遺留陰影,以前用別人的開源控件時總容易出點問題。現在這樣就是一個單純的適配器,不用考慮列表的布局是線性的還是網格的等類似問題。與界面無關,只干干凈凈的定義了邏輯規則,這種感覺太爽了~

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

SuperAdapter是什么

SuperAdapter 是對RecyclerView.Adapter進行封裝并將許多常用功能集成之中的一個Android庫。你不需要為每個列表單獨去寫ViewHolder后再聲明控件,當構建不需要重用的列表時你甚至不需要單獨創建類去繼承RecyclerView.Adapter定制適配器,只要在ActivityFragment中簡單寫下幾行代碼即可實現,有效的減化了復寫代碼的數量。

SuperAdapter目前有哪些功能

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

下載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

關于開源

如果你只是想了解如何使用它可以跳過本節。

在大學期間懵懵懂懂的聽老師說了一大堆開源的意義,雖然現在也還是不太能領會其中的精神,但是這幾年一路走來看過不少大神們的開源代碼,也算是在其中獲得不少的好處。

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

接下來我將繼續為這個小工具添加新的實用功能,當然如果使用過程中有問題可以添加到 Issues 中,我會經常看的。

GitHub地址傳送門:SuperAdapter

定義與聲明

如果你的列表只出現在了一個布局中并不會重復用使用,那么使用SuperAdapter時就不需要您單獨創建類繼承父類來定制化適配器,但是還是需要遵守一項規則:在ActivityFragment中聲明SuperAdapter屬性的同時需要聲明您的列表數據源類型:

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

如果你的列表會在多處重復使用,這時你需要將 SuperAdapter 封裝成自定義的 Adapter,下面會有專門一節講封裝 SuperAdapter 需要注意的事項,此處先不提。

綁定單一布局的列表

綁定單一布局列表的方法非常簡單,只需要在SuperAdapter的構造函數中添加布局文件的layoutId與數據源即可。

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) 中綁定列表每個條目中的控件并進行邏輯處理。其中方法參數ViewHolder itemView 為自定義 ViewHolder

通用ViewHolder

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

綁定多種布局的列表

綁定多種類型布局文件需要構造器MultiItemViewBuilder<T>來輔助適配器確定每個位置調用對應的布局文件。同理,聲明屬性同時需要聲明數據源類型:

MultiItemViewBuilder<DataEntity> multiItemViewBuilder;

MultiItemViewBuilder需要您實現兩個方法:

  • int getItemType(int position,T data),通過參數中的位置和數據源來判斷返回ItemView類型,ItemView類型的值需自定義。
  • int getLayoutId(int type),通過判斷itemType返回對應的布局文件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構造函數中即可:

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

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

 };

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

綁定列表點擊事件

綁定點擊事件:

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

綁定長按事件:

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

設置空數據視圖

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

mAdapter.setEmptyDataView(R.layout.empty_view);

在為列表添加了 Header 與 Footer 后即使數據源為空也不認為需要顯示空視圖

添加列表唯一頂部Header

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

自定義頭部控件的本質其實就是列表中的一個特殊ItemView,它永遠存在列表最上方的位置且跟隨列表滑動。

添加自定義頭部需要實現一個頭部構造器來管理自定義頭部布局:

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);
        }

};

構造器接口中有兩個回調方法:

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

最后,將實現好的頭部構造器添加到適配器中即可:

mAdapter.addHeader(builder);

添加列表分類Header

開發中。。。

這個功能有點費勁啊。。。

添加列表底部Footer

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

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

       /**
        * 非用于分頁加載更多數據狀態下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("這是個底部");
        }

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

       /**
        * 分頁加載更多數據 - “加載數據失敗” 狀態的界面邏輯處理
        *
        * @param msg 數據加載失敗的原因
        */
        @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);
        }

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

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

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

最后,將構造器添加到 SuperAdapter 中:

mAdapter.addFooter(builder);

簡易Footer構造器SimpleFooterBuilder

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

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

SimpleFooterBuilder構造方法中的4個參數依次對應著 Footer 的正常模式、正在加載、加載失敗、加載完畢這幾種狀態的提示信息。

分頁自動加載更多數據

注意!在設置分頁加載數據前一定要先添加Footer

在使用該功能時,用戶可以自己設置分頁請求數據的起始頁,每當列表滑動到底部的時候就會按之前設置的頁數依次累加請求新的數據。

設置分頁加載數據調用 SuperAdapter 的setPaginationData()方法就可以,但是在這之前需要一個加載數據監聽器LoadDataListener來管理數據的加載狀態,當列表需要加載新的數據時就會調用監聽器的onLoadingData()方法,該方法有兩個參數:

  • int loadPage 需要加載新數據的頁數
  • LoadDataStatus loadDataStatus 分頁加載數據的狀態控制器

分頁加載數據的狀態控制器LoadDataStatus 提供了三種狀態:

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

注意!加載數據一定要調用狀態控制器LoadDataStatus中的方法給列表加載狀態做反饋

具體的實現代碼:

// 分頁加載數據的起始頁
private static final int START_PAGE = 0;

...
...

// 實現加載數據監聽器
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("網絡請求出錯");
                        }                                
                });
        }
}

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

封裝 SuperAdapter 的注意事項

SuperAdaper類中提供了兩個有參構造方法:

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

他們實現了不同類型的子布局,所以繼承SuperAdapter時要Super()其中一個構造方法,但是你肯定不希望每次實例化適配器都要定義它們,所以建議在你的自定義類中將布局類型以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();
        }
    };

這樣自定義類的構造方法中就無需填寫參數:

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

或者

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

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,761評論 25 708
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽默認的外補...
    _Yfling閱讀 13,784評論 1 92
  • 近期一直在一個自憐的悲觀思緒中踐踏自己的自信,想想目前的生活過得不如意都是由于自己的決策失誤導致。一個完全交學費...
    墨道尋常閱讀 308評論 0 0
  • 火山 經歷雨水沖激三十二年 某個深夜 一匹流浪的狼 選擇跳進巖漿 怒目圓睜, 咬著冷冷的牙 一瞬間 巖漿被撞破了缺...
    關馨仁閱讀 339評論 0 2
  • 我,一個簡單普通的人。女性、職員、媽媽。 大學畢業后就職在湖南長沙某知名互聯網公司(上市企業,2000人及以上),...
    槑的陳閱讀 898評論 0 3