在項(xiàng)目中經(jīng)常需要進(jìn)行不同狀態(tài)的加載铁瞒,例如在網(wǎng)絡(luò)請(qǐng)求時(shí)的加載中狀態(tài),加載失敗狀態(tài)桅滋,沒有網(wǎng)絡(luò)狀態(tài)和沒有數(shù)據(jù)的狀態(tài)等慧耍,之前在項(xiàng)目中的做法是把幾個(gè)不同的狀態(tài)布局都添加到需要進(jìn)行狀態(tài)切換的Activity或Fragment的布局文件當(dāng)中,接著再對(duì)每一個(gè)狀態(tài)界面進(jìn)行相應(yīng)的隱藏顯示丐谋,但是在界面一多的情況下芍碧,重復(fù)操作就會(huì)顯得很繁瑣。
在進(jìn)行了無數(shù)次這樣繁瑣的操作后号俐,有些受不了了泌豆,就想著能不能把這幾種狀態(tài)都封裝到同一個(gè)View中,在需要顯示不同的狀態(tài)時(shí)只需要調(diào)用相應(yīng)的狀態(tài)方法就可以進(jìn)行切換萧落,這樣可比上一種方法簡(jiǎn)便得多践美,哈哈洗贰,這當(dāng)然是可以的,接下來就介紹一下NetworkStateView陨倡。
NetworkStateView繼承自LinearLayout敛滋,在里面定義了加載成功,加載中兴革,加載出錯(cuò)(這里只統(tǒng)一定義為網(wǎng)絡(luò)出錯(cuò)绎晃,當(dāng)然了用在哪種出錯(cuò)方式上可以由你自己決定),沒有網(wǎng)絡(luò)杂曲,沒有數(shù)據(jù)五種狀態(tài)庶艾,其中加載成功表示用來顯示Activity或Fragment的界面,并用變量mCurrentState來記住當(dāng)前顯示的狀態(tài)擎勘,相應(yīng)的變量值如下:
//當(dāng)前的加載狀態(tài)
private int mCurrentState;
private static final int STATE_SUCCESS = 0;
private static final int STATE_LOADING = 1;
private static final int STATE_NETWORK_ERROR = 2;
private static final int STATE_NO_NETWORK = 3;
private static final int STATE_EMPTY = 4;
接著需要自定義屬性咱揍,用于傳入對(duì)應(yīng)的狀態(tài)布局文件,在同一種狀態(tài)中如果需要有不同的界面顯示棚饵,便可以對(duì)應(yīng)的傳入layout文件煤裙,這樣可以方便擴(kuò)展
<declare-styleable name="NetworkStateView">
<!-- 加載中的布局id -->
<attr name="loadingView" format="reference" />
<!-- 加載錯(cuò)誤的布局id -->
<attr name="errorView" format="reference" />
<!-- 加載錯(cuò)誤的布局圖片 -->
<attr name="nsvErrorImage" format="reference" />
<!-- 加載錯(cuò)誤的布局文字 -->
<attr name="nsvErrorText" format="string" />
<!-- 沒有數(shù)據(jù)的布局id -->
<attr name="emptyView" format="reference" />
<!-- 沒有數(shù)據(jù)的布局圖片 -->
<attr name="nsvEmptyImage" format="reference" />
<!-- 沒有數(shù)據(jù)的布局文字 -->
<attr name="nsvEmptyText" format="string" />
<!-- 沒有網(wǎng)絡(luò)的布局id -->
<attr name="noNetworkView" format="reference" />
<!-- 沒有數(shù)據(jù)的布局圖片 -->
<attr name="nsvNoNetworkImage" format="reference" />
<!-- 沒有數(shù)據(jù)的布局文字 -->
<attr name="nsvNoNetworkText" format="string" />
<!-- 刷新的ImageView圖片id -->
<attr name="nsvRefreshImage" format="reference"/>
<!-- 文字大小 -->
<attr name="nsvTextSize" format="dimension" />
<!-- 文字顏色 -->
<attr name="nsvTextColor" format="color" />
</declare-styleable>
定義了屬性之后,需要在NetworkStateView的構(gòu)造函數(shù)中使用TypedArray進(jìn)行相應(yīng)屬性值的查找噪漾,注意硼砰,這里查找得到時(shí)布局文件的id,最后在顯示的時(shí)候需要對(duì)布局文件id進(jìn)行相應(yīng)的inflate欣硼,查找之后可以進(jìn)行一些基本屬性的設(shè)置题翰,例如LayoutParams和BackgroundColor等
public NetworkStateView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.NetworkStateView, defStyleAttr, 0);
mLoadingViewId = typedArray.getResourceId(R.styleable.NetworkStateView_loadingView, R.layout.view_loading);
mErrorViewId = typedArray.getResourceId(R.styleable.NetworkStateView_errorView, R.layout.view_network_error);
mNoNetworkViewId = typedArray.getResourceId(R.styleable.NetworkStateView_noNetworkView, R.layout.view_no_network);
mEmptyViewId = typedArray.getResourceId(R.styleable.NetworkStateView_emptyView, R.layout.view_empty);
....
typedArray.recycle();
mInflater = LayoutInflater.from(context);
params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
setBackgroundColor(getResources().getColor(R.color.white));
}
在使用屬性時(shí),可以直接在NetworkStateView的布局文件中進(jìn)行設(shè)置诈胜,又或者在styles文件中進(jìn)行設(shè)置
-
直接在布局文件中聲明
<com.zht.networkstateview.ui.widget.NetworkStateView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nsv_state_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" android:orientation="vertical" android:visibility="visible" app:emptyView="@layout/view_empty" app:errorView="@layout/view_network_error" app:loadingView="@layout/view_loading" app:noNetworkView="@layout/view_no_network" app:nsvTextColor="@color/gray_text_default" app:nsvTextSize="16sp"> </com.zht.networkstateview.ui.widget.NetworkStateView>
-
在styles文件進(jìn)行設(shè)置
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="styleNetworkStateView">@style/NetworkStateViewTheme</item> ... </style> <style name="NetworkStateViewTheme" parent="NetworkStateView.Style"> <item name="nsvTextSize">16sp</item> <item name="nsvTextColor">#ffffff</item> ... </style>
進(jìn)行上面的步驟后豹障,就可以加載相應(yīng)的布局文件了,以加載失敗(網(wǎng)絡(luò)出錯(cuò))為例耘斩,先定義一個(gè)showError方法沼填,在一開始需要將當(dāng)前狀態(tài)置為STATE_NETWORK_ERROR,接著inflate布局括授,注意坞笙,inflate之后需要進(jìn)行addView將加載失敗(網(wǎng)絡(luò)出錯(cuò))狀態(tài)的View添加到NetworkStateView中,這樣才可以進(jìn)行相應(yīng)的顯示隱藏操作
public void showError() {
mCurrentState = STATE_NETWORK_ERROR;
if (null == mErrorView) {
mErrorView = mInflater.inflate(mErrorViewId, null);
addView(mErrorView, 0, params);
}
showViewByState(mCurrentState);
}
showViewByState方法就是根據(jù)當(dāng)前的狀態(tài)來進(jìn)行相應(yīng)的View的切換
private void showViewByState(int state) {
//如果當(dāng)前狀態(tài)為加載成功荚虚,隱藏此View薛夜,反之顯示
this.setVisibility(state == STATE_SUCCESS ? View.GONE : View.VISIBLE);
if (null != mLoadingView) {
mLoadingView.setVisibility(state == STATE_LOADING ? View.VISIBLE : View.GONE);
}
if (null != mErrorView) {
mErrorView.setVisibility(state == STATE_NETWORK_ERROR ? View.VISIBLE : View.GONE);
}
if (null != mNoNetworkView) {
mNoNetworkView.setVisibility(state == STATE_NO_NETWORK ? View.VISIBLE : View.GONE);
}
if (null != mEmptyView) {
mEmptyView.setVisibility(state == STATE_EMPTY ? View.VISIBLE : View.GONE);
}
}
嗯...到這里其實(shí)也差不多了,不過還有一個(gè)問題版述,就是在加載失敗之后需要進(jìn)行刷新重新請(qǐng)求網(wǎng)絡(luò)怎么辦梯澜?哈哈,這當(dāng)然也是可以解決的渴析,我們只需要在定義一個(gè)刷新按鈕晚伙,并對(duì)外提供一個(gè)接口進(jìn)行調(diào)用就可以了吮龄,相應(yīng)的接口及方法為
public void setOnRefreshListener(OnRefreshListener listener) {
mRefreshListener = listener;
}
public interface OnRefreshListener {
void onRefresh();
}
那么showError可以改造如下
public void showError() {
mCurrentState = STATE_NETWORK_ERROR;
if (null == mErrorView) {
mErrorView = mInflater.inflate(mErrorViewId, null);
View errorRefreshView = mErrorView.findViewById(R.id.error_refresh_view);
if (null != errorRefreshView) {
errorRefreshView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (null != mRefreshListener) {
mRefreshListener.onRefresh();
}
}
});
}
addView(mErrorView, 0, params);
}
showViewByState(mCurrentState);
}
嗯,這樣就可以把多種狀態(tài)的View統(tǒng)一封裝在同一個(gè)View當(dāng)中咆疗,我們可以在Activity的布局文件中通過include
標(biāo)簽加入NetworkStateView
漓帚,接著我們只要調(diào)用NetworkStateView
的相關(guān)方法就可以進(jìn)行多種狀態(tài)的切換了
詳細(xì)的代碼以及Sample可以去我的github查看,覺得還可以的不妨Star或follow
古語(yǔ)有云:不懶的程序員不是好程序員午磁,有人會(huì)覺得這樣也只是比第一種方法稍微簡(jiǎn)便一些尝抖,但也是需要在每個(gè)界面的布局文件中通過include
標(biāo)簽進(jìn)行添加,并且進(jìn)行findViewById
的操作然后才能調(diào)用相關(guān)方法迅皇,能不能進(jìn)行統(tǒng)一的設(shè)置呢昧辽?可以不需要在每個(gè)界面中進(jìn)行include
,統(tǒng)一設(shè)置之后就可以調(diào)用相關(guān)方法登颓,這當(dāng)然也是可以的搅荞,我們可以在BaseActivity進(jìn)行相應(yīng)的設(shè)置,這樣子類Activity只需要調(diào)用BaseActivity的方法就可以挺据,至于怎么進(jìn)行設(shè)置取具,請(qǐng)等下篇講解。扁耐。。