轉載 AlarmZ [http://www.lxweimin.com/p/858d41972d15]
他還有一個一個Android的基礎項目 [https://github.com/AlarmZeng/BaseProject]
在項目中經常需要進行不同狀態的加載,例如在網絡請求時的加載中狀態,加載失敗狀態,沒有網絡狀態和沒有數據的狀態等,之前在項目中的做法是把幾個不同的狀態布局都添加到需要進行狀態切換的Activity或Fragment的布局文件當中,接著再對每一個狀態界面進行相應的隱藏顯示,但是在界面一多的情況下,重復操作就會顯得很繁瑣。
在進行了無數次這樣繁瑣的操作后,有些受不了了,就想著能不能把這幾種狀態都封裝到同一個View中,在需要顯示不同的狀態時只需要調用相應的狀態方法就可以進行切換,這樣可比上一種方法簡便得多,哈哈,這當然是可以的,接下來就介紹一下NetworkStateView。
NetworkStateView繼承自LinearLayout,在里面定義了加載成功,加載中,加載出錯(這里只統一定義為網絡出錯,當然了用在哪種出錯方式上可以由你自己決定),沒有網絡,沒有數據五種狀態,其中加載成功表示用來顯示Activity或Fragment的界面,并用變量mCurrentState來記住當前顯示的狀態,相應的變量值如下:
//當前的加載狀態
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;
接著需要自定義屬性,用于傳入對應的狀態布局文件,在同一種狀態中如果需要有不同的界面顯示,便可以對應的傳入layout文件,這樣可以方便擴展
<declare-styleable name="NetworkStateView">
<!-- 加載中的布局id -->
<attr name="loadingView" format="reference" />
<!-- 加載錯誤的布局id -->
<attr name="errorView" format="reference" />
<!-- 加載錯誤的布局圖片 -->
<attr name="nsvErrorImage" format="reference" />
<!-- 加載錯誤的布局文字 -->
<attr name="nsvErrorText" format="string" />
<!-- 沒有數據的布局id -->
<attr name="emptyView" format="reference" />
<!-- 沒有數據的布局圖片 -->
<attr name="nsvEmptyImage" format="reference" />
<!-- 沒有數據的布局文字 -->
<attr name="nsvEmptyText" format="string" />
<!-- 沒有網絡的布局id -->
<attr name="noNetworkView" format="reference" />
<!-- 沒有數據的布局圖片 -->
<attr name="nsvNoNetworkImage" format="reference" />
<!-- 沒有數據的布局文字 -->
<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的構造函數中使用TypedArray進行相應屬性值的查找,注意,這里查找得到時布局文件的id,最后在顯示的時候需要對布局文件id進行相應的inflate,查找之后可以進行一些基本屬性的設置,例如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));
}
在使用屬性時,可以直接在NetworkStateView的布局文件中進行設置,又或者在styles文件中進行設置
直接在布局文件中聲明
<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文件進行設置
<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>
進行上面的步驟后,就可以加載相應的布局文件了,以加載失敗(網絡出錯)為例,先定義一個showError方法,在一開始需要將當前狀態置為STATE_NETWORK_ERROR,接著inflate布局,注意,inflate之后需要進行addView將加載失敗(網絡出錯)狀態的View添加到NetworkStateView中,這樣才可以進行相應的顯示隱藏操作
public void showError() {
mCurrentState = STATE_NETWORK_ERROR;
if (null == mErrorView) {
mErrorView = mInflater.inflate(mErrorViewId, null);
addView(mErrorView, 0, params);
}
showViewByState(mCurrentState);
}
showViewByState方法就是根據當前的狀態來進行相應的View的切換
private void showViewByState(int state) {
//如果當前狀態為加載成功,隱藏此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);
}
}
嗯...到這里其實也差不多了,不過還有一個問題,就是在加載失敗之后需要進行刷新重新請求網絡怎么辦?哈哈,這當然也是可以解決的,我們只需要在定義一個刷新按鈕,并對外提供一個接口進行調用就可以了,相應的接口及方法為
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);
}
嗯,這樣就可以把多種狀態的View統一封裝在同一個View當中,我們可以在Activity的布局文件中通過include標簽加入NetworkStateView,接著我們只要調用NetworkStateView的相關方法就可以進行多種狀態的切換了