引言
從上學到工作,一晃搞Android也已經有幾年了,用的最多控件不外乎就那么幾個,其中列表控件用起來相對來說比較繁瑣,尤其是出了RecyclerView
之后。前段時間突發奇想做一個通用的適配器這樣就不用每次寫重復的東西了那多爽啊!這里還要好好感謝一下網上那些技術博客大神們,很多東西都是借鑒他們的思想。
為什么只封裝適配器呢?
雖說把RecyclerView
一起封裝就能做出更多好用的功能,用戶體驗也會更好,但可能是歷史遺留陰影,以前用別人的開源控件時總容易出點問題。現在這樣就是一個單純的適配器,不用考慮列表的布局是線性的還是網格的等類似問題。與界面無關,只干干凈凈的定義了邏輯規則,這種感覺太爽了~
最后,GitHub 上項目地址傳送門:SuperAdapter
SuperAdapter是什么
SuperAdapter 是對RecyclerView.Adapter
進行封裝并將許多常用功能集成之中的一個Android庫。你不需要為每個列表單獨去寫ViewHolder
后再聲明控件,當構建不需要重用的列表時你甚至不需要單獨創建類去繼承RecyclerView.Adapter
定制適配器,只要在Activity
或Fragment
中簡單寫下幾行代碼即可實現,有效的減化了復寫代碼的數量。
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
時就不需要您單獨創建類繼承父類來定制化適配器,但是還是需要遵守一項規則:在Activity
或Fragment
中聲明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
}