最近碰到一個新的頁面控制需求:下拉刷新如果失敗擒抛,listview上面的數(shù)據(jù)需要保留推汽,然后悲劇的發(fā)現(xiàn)之前寫的NetFragment和ListNetFragment都不能覆蓋這種邏輯,又要重寫了歧沪。痛定思痛民泵,我發(fā)現(xiàn)問題的本質(zhì)在于 控制邏輯C 和 頁面展示V 沒有真正分離,因此決定先把邏輯從Fragment里面抽取出來槽畔。
一栈妆、建立狀態(tài)模型
所謂的邏輯,在這里是一個狀態(tài)機厢钧,包含的狀態(tài)如下:
- 請求數(shù)據(jù)狀態(tài):請求前鳞尔、請求中、請求結(jié)束
- 請求后的狀態(tài)有五種:
- 無法訪問網(wǎng)絡(luò)早直;
- 服務(wù)器連接失斄燃佟;
- 請求參數(shù)異常;
- 數(shù)據(jù)為空霞扬;
- 獲取到有效數(shù)據(jù)糕韧,如果是list意味著長度大于0;
建立狀態(tài)模型如下:
public enum STATE {
ASK_PRE(-3), // 請求前
ASK_ING(-2), // 請求中
ASK_ED(-1), // 請求結(jié)束
ASK_ED_CANNOT_ACCESS(0), //無法訪問網(wǎng)絡(luò)枫振;
ASK_ED_FAIL(1), //服務(wù)器連接失敗萤彩;
ASK_ED_ERROR(2), //請求參數(shù)異常
ASK_ED_EMPTY(3), //數(shù)據(jù)為空粪滤;
ASK_ED_AVAILABILITY(4),; //獲取到有效數(shù)據(jù)
final int value;
STATE(int i) {
value = i;
}
}
二、寫控制邏輯
基本思路:根據(jù)網(wǎng)絡(luò)請求結(jié)果雀扶,控制八種狀態(tài)的流轉(zhuǎn)杖小,至于上層如何處理這些狀態(tài)下的布局,底層完全不控制愚墓。最后從NetFragment中剝離出NetController予权。
/**
* Created by shitianci on 16/8/23.
*/
public abstract class NetController<T extends NetResultInfo> {
private static final String TAG = NetController.class.getSimpleName();
protected final Context mContext;
boolean loadingNetData = false;
public NetController(Context context) {
mContext = context;
}
public void loadNetData() {
Log.d(TAG, "loadNetData, loadingNetData = " + loadingNetData);
showData(STATE.ASK_PRE, null);
if (loadingNetData) {
showData(STATE.ASK_ED, null);
return;
}
if (!BaseRepositoryCollection.tryToDetectNetwork(mContext)) {
showData(STATE.ASK_ED, null);
showData(STATE.ASK_ED_CANNOT_ACCESS, null);
return;
}
SimpleSafeTask<T> netTask = new SimpleSafeTask<T>(mContext) {
protected void onPreExecuteSafely() throws Exception {
loadingNetData = true;
showData(STATE.ASK_ING, null);
}
@Override
protected T doInBackgroundSafely() throws Exception {
T result = onDoInBackgroundSafely();
return result;
}
@Override
protected void onPostExecuteSafely(T result, Exception e) {
showData(STATE.ASK_ED, null);
super.onPostExecuteSafely(result, e);
loadingNetData = false;
if (e != null || result == null) {
showData(STATE.ASK_ED_FAIL, result);
return;
}
if (result.getRespCode() != NetResultInfo.RETURN_CODE_000000) {
showData(STATE.ASK_ED_ERROR, result);
return;
}
if (isEmpty(result)){
showData(STATE.ASK_ED_EMPTY, result);
return;
}
showData(STATE.ASK_ED_AVAILABILITY, result);
}
protected void onCancelled() {
loadingNetData = false;
showData(STATE.ASK_ED, null);
showData(STATE.ASK_ED_FAIL, null);
}
};
netTask.execute();
return;
}
/**
* -------------------------
* START: 最重要的流程方法
* -------------------------
*/
/**
* 加載后臺數(shù)據(jù)
*
* @return
*/
protected abstract T onDoInBackgroundSafely();
/**
* 數(shù)據(jù)是否為空
* 針對list情形,如果list size為0浪册,則返回為true扫腺。
* @param result
* @return
*/
protected abstract boolean isEmpty(T result);
/**
* 返回狀態(tài)
* @param state
* @param result
*/
protected abstract void showData(STATE state, T result);
/**
* -------------------------
* END
* -------------------------
*/
}
難點:ListView頂部的數(shù)據(jù)如何處理?
第一種思路是嵌套村象,形成如下結(jié)構(gòu):
SwipeRefreshLayout
* ScrollView
* LinearLayout
* topView
* ListView
經(jīng)過實踐斧账,發(fā)現(xiàn)ListView無法獲取到上拉的動作,因此無法加載下一頁煞肾,這跟SwipeRefreshLayout的限制有關(guān)咧织,SwipeRefreshLayout只能處理第一層的子view,所以此路不通籍救。
第二種思路是把topView作為ListView的HeaderView习绢,
SwipeRefreshLayout
* ListView
* topView
這樣處理之后層級就變得極為簡單。經(jīng)過檢驗蝙昙,滑動正常闪萄,剩下的問題是,如何在數(shù)據(jù)異常時奇颠,顯示異常界面败去?——利用ArrayAdapter加載不同數(shù)據(jù)類型的特性,把異常界面當(dāng)做不通類型的數(shù)據(jù)來進行適配烈拒。經(jīng)過檢驗圆裕,顯示正常。抽取出來的ListNetController代碼如下:
/**
* Created by shitianci on 16/8/23.
*/
public abstract class ListNetController<O extends BaseListModel> extends NetController<ListNetResultInfo<O>> {
private String TAG = ListNetController.class.getSimpleName();
public enum Type {
REFRESH,
LOAD_MORE,
}
private final SwipeRefreshLayout mSwipeRefreshLayout;
private SwipeRefreshHelper mSwipeRefreshHelper;
private final ListView mListView;
private final O mMockData; //用于模擬不正常的數(shù)據(jù)
public DataAdapter mDataAdapter = null;
Type type = Type.REFRESH;
public ListNetController(Context context, SwipeRefreshLayout swipeRefreshLayout, ListView listView) {
this(context, swipeRefreshLayout, listView, null, null);
}
/**
*
* @param context 上下文環(huán)境
* @param swipeRefreshLayout 下拉刷新布局
* @param listView 列表布局
* @param headerView 頭部顯示布局
* @param abnormalData 異常數(shù)據(jù)荆几,上層new一個即可吓妆。
*/
public ListNetController(Context context, SwipeRefreshLayout swipeRefreshLayout, ListView listView, View headerView, O abnormalData) {
super(context);
mSwipeRefreshLayout = swipeRefreshLayout;
mListView = listView;
mListView.addHeaderView(headerView);
mDataAdapter = new DataAdapter(mContext);
mListView.setAdapter(mDataAdapter);
mMockData = abnormalData;
configRefreshLayout();
}
/**
* 下拉刷新數(shù)據(jù)
*/
public void refresh() {
mSwipeRefreshHelper.autoRefresh();
}
protected void configRefreshLayout() {
//!6种行拢!為了保證能夠加載下一頁,這個方法必須調(diào)用诞吱,且必須放在mSwipeRefreshHelper初始化前面舟奠,具體原因看代碼
mSwipeRefreshLayout.setColorSchemeColors(panda.android.lib.R.color.app_primary);
mSwipeRefreshHelper = new SwipeRefreshHelper(mSwipeRefreshLayout);
// try {
// Field field = mSwipeRefreshHelper.getClass().getDeclaredField("mContentView");
// field.setAccessible(true);
// field.set(mSwipeRefreshHelper, listView); //修改內(nèi)容
// } catch (Exception e) {
// e.printStackTrace();
// }
mSwipeRefreshHelper.setLoadMoreEnable(true);
mSwipeRefreshHelper.setOnSwipeRefreshListener(new SwipeRefreshHelper.OnSwipeRefreshListener() {
@Override
public void onfresh() {
Log.d(TAG, "下拉刷新");
// mDataAdapter = null;
loadNetData();
isLoadedAllNetData = false;
type = Type.REFRESH;
}
});
mSwipeRefreshHelper.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void loadMore() {
Log.d(TAG, "加載更多, isLoadedAllNetData = " + isLoadedAllNetData);
if (isLoadedAllNetData) {
mSwipeRefreshHelper.loadMoreComplete(false);
return;
}
loadNetData();
type = Type.LOAD_MORE;
}
});
}
public boolean isLoadedAllNetData = false; //是否所有的數(shù)據(jù)都加載完成
@Override
protected boolean isEmpty(ListNetResultInfo result) {
if (result.getList().isEmpty()){
return true;
}
return false;
}
/**
* 樣例代碼竭缝,僅供參考
* @param state
* @param result
*/
@Override
protected void showData(BaseListModel.STATE state, ListNetResultInfo<O> result) {
switch (state) {
case ASK_PRE:
break;
case ASK_ING:
break;
case ASK_ED:
break;
case ASK_ED_CANNOT_ACCESS:
case ASK_ED_FAIL:
case ASK_ED_ERROR:
if (mDataAdapter.getCount() == 0){
//顯示虛擬布局
mDataAdapter.clear();
mMockData.state = state;
mDataAdapter.add(mMockData);
mSwipeRefreshHelper.setLoadMoreEnable(false);
mSwipeRefreshHelper.loadMoreComplete(false);
}
break;
case ASK_ED_EMPTY:
case ASK_ED_AVAILABILITY:
List<O> list = result.getList();
for (int i = 0; i < list.size(); i++) {
mDataAdapter.add(list.get(i));
}
mDataAdapter.notifyDataSetChanged();
if (false){
isLoadedAllNetData = true;
mSwipeRefreshHelper.setNoMoreData();
}
else{
isLoadedAllNetData = false;
}
break;
}
switch (type) {
case REFRESH:
mSwipeRefreshHelper.refreshComplete();
mSwipeRefreshHelper.setLoadMoreEnable(true);
break;
case LOAD_MORE:
mSwipeRefreshHelper.loadMoreComplete(isLoadedAllNetData);
mSwipeRefreshHelper.setLoadMoreEnable(!isLoadedAllNetData);
break;
}
}
//不同的訂單的適配器
public class DataAdapter extends ArrayAdapter<O> {
public DataAdapter(Context context) {
super(context, getItemLayoutId(), getItemTextViewResourceId());
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
O item = getItem(position);
View result = bindView(position, super.getView(position, convertView, parent), parent);
switch (item.state){
case ASK_ED_CANNOT_ACCESS:
case ASK_ED_FAIL:
case ASK_ED_ERROR:
case ASK_ED_EMPTY:
result = getAbnormalView(item.state);
break;
case ASK_ED_AVAILABILITY:
}
return result;
}
@Override
public int getViewTypeCount() {
return BaseListModel.STATE.ASK_ED_AVAILABILITY.value+1;
}
@Override
public int getItemViewType(int position) {
O item = getItem(position);
return item.state.value;
}
}
TextView abnormalView = new TextView(mContext);
/**
* 返回異常狀態(tài)下的顯示布局
* @param state
* @return
*/
public View getAbnormalView(BaseListModel.STATE state) {
switch (state){
case ASK_ED_CANNOT_ACCESS:
abnormalView.setText("無法訪問網(wǎng)絡(luò)");
break;
case ASK_ED_FAIL:
abnormalView.setText("無法訪問服務(wù)器");
break;
case ASK_ED_ERROR:
abnormalView.setText("請求參數(shù)錯誤");
break;
case ASK_ED_EMPTY:
abnormalView.setText("數(shù)據(jù)為空");
break;
}
return abnormalView;
}
/**
* 加載list數(shù)據(jù)
*
* @param startIndex 起始數(shù)據(jù)項
* @param pageSize 預(yù)期加載多少項
* @return
*/
protected abstract ListNetResultInfo<O> onDoInBackgroundSafely(int startIndex, int pageSize);
/**
* 獲取item布局中一個textview的id
*
* @return
*/
public abstract int getItemTextViewResourceId();
/**
* 獲取對應(yīng)Item布局的id
*
* @return
*/
public abstract int getItemLayoutId();
/**
* 將view和數(shù)據(jù)綁定
*
* @param position
* @param view
* @param parent
* @return
*/
public abstract View bindView(int position, View view, ViewGroup parent);
/**
* -------------------------
* END
* -------------------------
*/
}
Panda
2016-08-24