引言
從上學(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
定制適配器,只要在Activity
或Fragment
中簡單寫下幾行代碼即可實(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ī)則:在Activity
或Fragment
中聲明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
}