電子市場總結(二)
1、封裝一個LoadingPager
什麼是LoadingPager?
在項目中我們可以看到,大多數頁面都有五種情況發生
- 未加載
- 正在加載(轉圈的那種)
- 加載成功了
- 加載失敗了
- 加載成功但是沒有任何數據
可以想象,我們的項目中需要大量的去構建這樣的界面,如果我們簡單只是將每個狀態對應的頁面先寫完整,再在每一個Fragment中整合那樣的工作量很大,而且不易維護。
此時爲了適應這種大規模使用的現象,我們利用一個具備五種狀態的FrameLayout來簡化這些操作。
自定義LoadingPager的詳細解釋
1.自定義一個FrameLayout,命名爲LoadingPager
2.五種數據加載的狀態
public static final int
STATE_EMPTY = 0, // 未加載
STATE_ERROR = -1, // 加載失敗
STATE_SUCCESS = 1, // 加載成功
STATE_LOADING = 2, // 正在加載(轉圈的那種)
STATE_NO_DATA = -2; // 沒有加載到數據(加載成功的第二種狀態)
同時五種狀態對應五種佈局
private View mEmptyLayout, // 空佈局
mErrorLayout, // 錯誤佈局
mLoadLayout, // 加載中佈局
mNoDataLayout, // 未加載到數據的佈局
mSuccessLayout; //成功的佈局,由調用者自定義
3.加載成功的佈局
1.控制顯示
可以看出LoadingPager雖然可以封裝這些狀態,並且可以根據不同的狀態展示不同的佈局,但是這些佈局仍然可以自定義,並由調用者根據自己的需求更換對應狀態的佈局。
因此可以暴漏一些方法來讓調用者去自定義這些佈局。此處僅對成功的佈局進行了拓展,當我們的狀態變爲 STATE_SUCCESS 時,切換成調用者提供的佈局。代碼如下
/**
* 展示當前的狀態對應的佈局,隱藏其他佈局
*/
private void showCurrentLayout() {
mEmptyLayout.setVisibility(currentState == STATE_EMPTY ? VISIBLE : GONE);
mErrorLayout.setVisibility(currentState == STATE_ERROR ? VISIBLE : GONE);
mLoadLayout.setVisibility(currentState == STATE_LOADING ? VISIBLE : GONE);
mNoDataLayout.setVisibility(currentState == STATE_NO_DATA ? VISIBLE : GONE);
// 加載 數據成功 且是在佈局初次加載的狀態
if (currentState == STATE_SUCCESS && mSuccessLayout == null) {
mSuccessLayout = initSuccessLayout();
// 所加載的佈局不能爲空
if (mSuccessLayout != null)
addView(mSuccessLayout);
}
// 當加載成功的佈局成功設置後,對其狀態進行判斷
if (mSuccessLayout != null)
mSuccessLayout.setVisibility(currentState == STATE_SUCCESS ? VISIBLE : GONE);
}
通過使用抽象方法的形式,強制要求調用者去實現加載成功的佈局
2.控制當前狀態
提供設置狀態的方法,讓調用者可以在自己想要的時間裏去展示成功的佈局
/**
* 設置Loadingpager當前的狀態
*
*/
public void setCurrentState(int state) {
// 先判斷當前結果是否爲空
this.currentState = state;
showCurrentLayout();
}
3.重新加載的方法
暴漏一個可以設置重新加載事件的方法
public void setOnReloadClickListener(OnClickListener cl){
btn_load_again.setOnClickListener(cl);
}
2、封裝一個BaseFragment
1.爲什麼還要封裝一個BaseFragment?
在前面我們已經封裝了一個LoadingPager,我們可以很方便的控制這個自定義view去設置當前的狀態和佈局。
那麼這個view應該在那裏顯示呢?view本身不能去控制當前的狀態,調用者控制其顯示的邏輯又在哪裏呢?
提出這兩個問題的同時,我們還要考慮,這麼多的fragment他們都要用到LoadingPager因此我們應將這個兩個問題的解決方法統一的封裝起來,在一個BaseFragment中去處理,其他想要使用這種LoadingPager模式的Fragment只要繼承BaseFragment就可以只關心自己的數據加載和界面展示了。
2.具體的封裝
1.onCreateView中去展示LoadingPager,同時要求子類實現initSuccessLayout()方法來設置加載成功的佈局,因爲BaseFragment雖然實現了LoadingPager,但是他仍然需要將這個問題交給子類。
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//統一使用自定義的具備狀態的loadingpager
mRootLayout = new LoadingPager(mActivity) {
@Override
public View initSuccessLayout() {
mSuccessLayout = BaseFragment.this.initSuccessLayout();
return mSuccessLayout;
}
};
//設置重新加載的監聽器
mRootLayout.setOnReloadClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loading();
}
});
return mRootLayout;
}
2.同時我們可以將Activity在BaseFragment中拿出來,這樣子類可以直接使用
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.mActivity = getActivity();
}
3.加載數據的邏輯
這裏考慮到所有的數據加載都需要IO處理或者是通過網絡來加載,那麼可以BaseFragment裏使用子線程這件事統一包辦了,子類只需要完成 loadData() 方法返回他的加載狀態即可。子類來返回加載狀態的原因是,BaseFragment並不知道是怎麼去加載的,所以這個邏輯仍然要拋給子類。
/**
* BaseFragment統一具備的加載方法,子線程加載
*/
public void loading() {
// 考慮到大多數的加載過程都是請求網絡,所以採用子線程
ThreadManager.ThreadPool instance = ThreadManager.getInstance();
instance.execute(new Runnable() {
@Override
public void run() {
// 子類加載後返回對應的狀態,如何去控制顯示的問題父類來解決即可
final BaseFragment.State state = loadData();
// 設置loadingpager的狀態
UIUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
if (mRootLayout != null && state != null)
mRootLayout.setCurrentState(state.getState());
}
});
}
});
}
4.爲子類中對數據的加載成功與否提供一個默認的判斷
/**
* 提供給子類進行檢查初次加載成功與否
*
*/
protected State checkLoad(List list) {
if (list != null) {
if (list.size() == 0)
return State.STATE_NO_DATA;
else
return State.STATE_SUCCESS;
}
return State.STATE_ERROR;
}
5.重新加載的邏輯,即對重新加載的按鈕設置點擊事件
//設置重新加載的監聽器
mRootLayout.setOnReloadClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loading();
}
});
6.總結,子類需要完成這些事情
- protected abstract View initSuccessLayout(); // 加載成功後是什麼樣的佈局
- protected abstract State loadData(); // 加載數據的邏輯
- protected abstract void initListener(); // 監聽的初始化
封裝完成後的效果如下:
3、其他的知識點
1.枚舉的用法
枚舉他實際相當於一個變相的class類,他可以設置構造器,但是他的主要目的還是定一些不會被改變的值,如下將LoadingPager對應的狀態進行了封裝,那麼子類在加載數據的時候就可以只返回枚舉值而不是int數值了:
/**
* 枚舉的用法
*/
public static enum State {
STATE_ERROR(LoadingPager.STATE_ERROR),
STATE_LOADING(LoadingPager.STATE_LOADING),
STATE_SUCCESS(LoadingPager.STATE_SUCCESS),
STATE_NO_DATA(LoadingPager.STATE_NO_DATA);
int state;
// 枚舉的構造器實際上只能自己使用,可以通過這種方式,爲自己設置數據
State(int state) {
this.state = state;
}
// 獲取枚舉存儲的數據,調用者可以通過get方式去獲取這個數據
int getState() {
return state;
}
}
4、封裝思想的總結
LoadingPager和BaseFragment的封裝抽取思想僅僅是整個項目的開始,其中LoadingPager是對佈局顯示的封裝,BaseFragment是對如何使用LoadingPager和控制LoadingPager的封裝。
通過這兩層封裝,子類所有集成自BaseFragment的對象只需要關心自己要顯示什麼和數據的加載即可。
在這裏並沒有涉及任何新的知識點,而是基本的封裝思想的搭建。