前言
在Android項(xiàng)目開發(fā)中一個(gè)界面的顯示狀態(tài)包括好幾種:內(nèi)容界面剑辫,loading界面干旧,網(wǎng)絡(luò)錯(cuò)誤界面等等;以前開發(fā)的時(shí)候都是直接把這些界面include到main界面中揭斧,然后動(dòng)態(tài)去切換界面,后來發(fā)現(xiàn)這樣處理不容易復(fù)用到其他項(xiàng)目中,而且在activity中處理這些狀態(tài)的顯示和隱藏比較亂讹开,所以就想著能不能封裝一個(gè)類來管理這些狀態(tài)View的切換盅视。
思路
為了讓View狀態(tài)的切換和Activity徹底分離開,必須把這些狀態(tài)View都封裝到一個(gè)管理類中旦万,然后暴露出幾個(gè)方法來實(shí)現(xiàn)View之間的切換闹击,因?yàn)樵诓煌捻?xiàng)目中可以需要的View也不一樣,所以考慮把管理類設(shè)計(jì)成builder模式來自由的添加需要的狀態(tài)View成艘。
實(shí)現(xiàn)
通常一個(gè)界面會(huì)包括:內(nèi)容赏半,空數(shù)據(jù),異常錯(cuò)誤淆两,加載断箫,網(wǎng)絡(luò)錯(cuò)誤等5種狀態(tài)的View,所以我們就設(shè)置這5種狀態(tài)View的切換
public static final class Builder {
private Context context;
private int loadingLayoutResId;
private int contentLayoutResId;
private ViewStub netWorkErrorVs;
private int netWorkErrorRetryViewId;
private ViewStub emptyDataVs;
private int emptyDataRetryViewId;
private ViewStub errorVs;
private int errorRetryViewId;
private int retryViewId;
private OnShowHideViewListener onShowHideViewListener;
private OnRetryListener onRetryListener;
public Builder(Context context) {
this.context = context;
}
public Builder loadingView(@LayoutRes int loadingLayoutResId) {
this.loadingLayoutResId = loadingLayoutResId;
return this;
}
public Builder netWorkErrorView(@LayoutRes int newWorkErrorId) {
netWorkErrorVs = new ViewStub(context);
netWorkErrorVs.setLayoutResource(newWorkErrorId);
return this;
}
public Builder emptyDataView(@LayoutRes int noDataViewId) {
emptyDataVs = new ViewStub(context);
emptyDataVs.setLayoutResource(noDataViewId);
return this;
}
public Builder errorView(@LayoutRes int errorViewId) {
errorVs = new ViewStub(context);
errorVs.setLayoutResource(errorViewId);
return this;
}
public Builder contentView(@LayoutRes int contentLayoutResId) {
this.contentLayoutResId = contentLayoutResId;
return this;
}
public Builder netWorkErrorRetryViewId(int netWorkErrorRetryViewId) {
this.netWorkErrorRetryViewId = netWorkErrorRetryViewId;
return this;
}
public Builder emptyDataRetryViewId(int emptyDataRetryViewId) {
this.emptyDataRetryViewId = emptyDataRetryViewId;
return this;
}
public Builder errorRetryViewId(int errorRetryViewId) {
this.errorRetryViewId = errorRetryViewId;
return this;
}
public Builder retryViewId(int retryViewId) {
this.retryViewId = retryViewId;
return this;
}
public Builder onShowHideViewListener(OnShowHideViewListener onShowHideViewListener) {
this.onShowHideViewListener = onShowHideViewListener;
return this;
}
public Builder onRetryListener(OnRetryListener onRetryListener) {
this.onRetryListener = onRetryListener;
return this;
}
public StatusLayoutManager build() {
return new StatusLayoutManager(this);
}
}
狀態(tài)管理類用到了建造者模式秋冰,上面是builder內(nèi)部類仲义,總共有11個(gè)屬性,loadingLayoutResId和contentLayoutResId代表等待加載和顯示內(nèi)容的xml文件剑勾;netWorkErrorVs埃撵,emptyDataVs,errorVs代表另外幾種異常狀態(tài)虽另,那為什么這幾種狀態(tài)要用ViewStub暂刘,因?yàn)樵诮缑鏍顟B(tài)切換中l(wèi)oading和內(nèi)容View都是一直需要加載顯示的,但是其他的3個(gè)只有在沒數(shù)據(jù)或者網(wǎng)絡(luò)異常的情況下才會(huì)加載顯示捂刺,所以用ViewStub來加載他們可以提高性能谣拣。
在錯(cuò)誤的幾個(gè)界面需要重試按鈕重新加載數(shù)據(jù),netWorkErrorRetryViewId, emptyDataRetryViewId, errorRetryViewId分別為幾個(gè)狀態(tài)界面重試按鈕的id叠萍, 如果這幾個(gè)按鈕的id是一樣的話就直接給retryViewId屬性賦值即可芝发,retryViewId優(yōu)先級最高。
onShowHideViewListener為狀態(tài)View顯示隱藏監(jiān)聽事件
onRetryListener為重試加載按鈕的監(jiān)聽事件
接下來需要把這些View添加到一個(gè)根View中返回給Activity苛谷,為了方便顯示隱藏這些View辅鲸,我們在根View中定義一個(gè)集合屬性,然后把這些View添加到集合當(dāng)中管理
/** * 存放布局集合 */
private SparseArray<View> layoutSparseArray = new SparseArray();
這個(gè)集合Key為id腹殿,Value為View独悴,id為根View類內(nèi)部自定義的id,通過id找到對應(yīng)的View來顯示隱藏View锣尉,下面通過一個(gè)方法來看下它的切換邏輯
/** * 顯示空數(shù)據(jù) */
public void showEmptyData() {
if(inflateLayout(LAYOUT_EMPTYDATA_ID))
showHideViewById(LAYOUT_EMPTYDATA_ID);
}
首先調(diào)用inflateLayout方法刻炒,方法返回true然后調(diào)用showHideViewById方法,
下面來看看inflateLayout方法的實(shí)現(xiàn)
private boolean inflateLayout(int id) {
boolean isShow = true;
if(layoutSparseArray.get(id) != null) return isShow;
switch (id) {
case LAYOUT_NETWORK_ERROR_ID:
if(mStatusLayoutManager.netWorkErrorVs != null) {
View view = mStatusLayoutManager.netWorkErrorVs.inflate();
retryLoad(view, mStatusLayoutManager.netWorkErrorRetryViewId);
layoutSparseArray.put(id, view);
isShow = true;
} else {
isShow = false;
}
break;
case LAYOUT_ERROR_ID:
if(mStatusLayoutManager.errorVs != null) {
View view = mStatusLayoutManager.errorVs.inflate();
retryLoad(view, mStatusLayoutManager.errorRetryViewId);
layoutSparseArray.put(id, view);
isShow = true;
} else {
isShow = false;
}
break;
case LAYOUT_EMPTYDATA_ID:
if(mStatusLayoutManager.emptyDataVs != null) {
View view = mStatusLayoutManager.emptyDataVs.inflate();
retryLoad(view, mStatusLayoutManager.emptyDataRetryViewId);
layoutSparseArray.put(id, view);
isShow = true;
} else {
isShow = false;
}
break;
}
return isShow;
}
方法里面通過id判斷來執(zhí)行不同的代碼自沧,首先判斷ViewStub是否為空坟奥,如果為空就代表沒有添加這個(gè)View就返回false树瞭,不為空就加載View并且添加到集合當(dāng)中,然后調(diào)用showHideViewById方法顯示隱藏View爱谁,retryLoad方法是給重試按鈕添加事件晒喷,先來看看showHideViewById方法邏輯
private void showHideViewById(int id) {
for(int i = 0; i < layoutSparseArray.size(); i++) {
int key = layoutSparseArray.keyAt(i);
View valueView = layoutSparseArray.valueAt(i);
//顯示該view
if(key == id) {
valueView.setVisibility(View.VISIBLE);
if(mStatusLayoutManager.onShowHideViewListener != null)
mStatusLayoutManager.onShowHideViewListener.onShowView(valueView, key);
} else {
if(valueView.getVisibility() != View.GONE) {
valueView.setVisibility(View.GONE);
if(mStatusLayoutManager.onShowHideViewListener != null)
mStatusLayoutManager.onShowHideViewListener.onHideView(valueView, key);
}
}
}
}
通過id找到需要顯示的View并且顯示它,隱藏其他View访敌,如果顯示隱藏監(jiān)聽事件不為空凉敲,就分別調(diào)用它的顯示和隱藏方法,下面再來看看retryLoad方法
public void retryLoad(View view, int id) {
View retryView = view.findViewById(mStatusLayoutManager.retryViewId != 0 ? mStatusLayoutManager.retryViewId : id);
if(retryView == null || mStatusLayoutManager.onRetryListener == null) return;
retryView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mStatusLayoutManager.onRetryListener.onRetry();
}
});
}
可以看出retryViewId 的優(yōu)先級最好寺旺,如果它不為0爷抓,就用它去查找View實(shí)例,然后如果View實(shí)例和重試監(jiān)聽都不為空就添加點(diǎn)擊事件阻塑,點(diǎn)擊事件里調(diào)用onRetryListener監(jiān)聽的onRetry方法蓝撇。
使用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initToolBar();
LinearLayout mainLinearLayout = (LinearLayout) findViewById(R.id.main_rl);
statusLayoutManager = StatusLayoutManager.newBuilder(this)
.contentView(R.layout.activity_content)
.emptyDataView(R.layout.activity_emptydata)
.errorView(R.layout.activity_error)
.loadingView(R.layout.activity_loading)
.netWorkErrorView(R.layout.activity_networkerror)
.retryViewId(R.id.button_try)
.onShowHideViewListener(new OnShowHideViewListener() {
@Override
public void onShowView(View view, int id) {
}
@Override
public void onHideView(View view, int id) {
}
}).onRetryListener(new OnRetryListener() {
@Override
public void onRetry() {
statusLayoutManager.showLoading();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
statusLayoutManager.showContent();
}
});
}
}).start();
}
}).build();
mainLinearLayout.addView(statusLayoutManager.getRootLayout(), 1);
statusLayoutManager.showLoading();
}
StatusLayoutManager提供了一系列的方法來顯示不同布局View之間的切換
statusLayoutManager.showLoading(); 顯示loading加載view
statusLayoutManager.showContent(); 顯示你的內(nèi)容view
statusLayoutManager.showEmptyData(); 顯示空數(shù)據(jù)view
statusLayoutManager.showError(); 顯示error view
statusLayoutManager.showNetWorkError(); 顯示網(wǎng)絡(luò)異常view
結(jié)束語
至此,核心邏輯和代碼都已經(jīng)分析完成叮姑,想看如何調(diào)用和源碼的朋友可以移步至:https://github.com/chenpengfei88/StatusLayout
我還有一篇封裝底部導(dǎo)航欄的文章唉地,大家有興趣也可以看看
http://www.reibang.com/p/7cccb5c054da#