最近碰到一个新的页面控制需求:下拉刷新如果失败,listview上面的数据需要保留,然后悲剧的发现之前写的NetFragment和ListNetFragment都不能覆盖这种逻辑,又要重写了。痛定思痛,我发现问题的本质在于 控制逻辑C 和 页面展示V 没有真正分离,因此决定先把逻辑从Fragment里面抽取出来。
一、建立状态模型
所谓的逻辑,在这里是一个状态机,包含的状态如下:
- 请求数据状态:请求前、请求中、请求结束
- 请求后的状态有五种:
- 无法访问网络;
- 服务器连接失败;
- 请求参数异常;
- 数据为空;
- 获取到有效数据,如果是list意味着长度大于0;
建立状态模型如下:
public enum STATE {
ASK_PRE(-3), // 请求前
ASK_ING(-2), // 请求中
ASK_ED(-1), // 请求结束
ASK_ED_CANNOT_ACCESS(0), //无法访问网络;
ASK_ED_FAIL(1), //服务器连接失败;
ASK_ED_ERROR(2), //请求参数异常
ASK_ED_EMPTY(3), //数据为空;
ASK_ED_AVAILABILITY(4),; //获取到有效数据
final int value;
STATE(int i) {
value = 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: 最重要的流程方法
* -------------------------
*/
/**
* 加载后台数据
*
* @return
*/
protected abstract T onDoInBackgroundSafely();
/**
* 数据是否为空
* 针对list情形,如果list size为0,则返回为true。
* @param result
* @return
*/
protected abstract boolean isEmpty(T result);
/**
* 返回状态
* @param state
* @param result
*/
protected abstract void showData(STATE state, T result);
/**
* -------------------------
* END
* -------------------------
*/
}
难点:ListView顶部的数据如何处理?
第一种思路是嵌套,形成如下结构:
SwipeRefreshLayout
* ScrollView
* LinearLayout
* topView
* ListView
经过实践,发现ListView无法获取到上拉的动作,因此无法加载下一页,这跟SwipeRefreshLayout的限制有关,SwipeRefreshLayout只能处理第一层的子view,所以此路不通。
第二种思路是把topView作为ListView的HeaderView,
SwipeRefreshLayout
* ListView
* topView
这样处理之后层级就变得极为简单。经过检验,滑动正常,剩下的问题是,如何在数据异常时,显示异常界面?——利用ArrayAdapter加载不同数据类型的特性,把异常界面当做不通类型的数据来进行适配。经过检验,显示正常。抽取出来的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; //用于模拟不正常的数据
public DataAdapter mDataAdapter = null;
Type type = Type.REFRESH;
public ListNetController(Context context, SwipeRefreshLayout swipeRefreshLayout, ListView listView) {
this(context, swipeRefreshLayout, listView, null, null);
}
/**
*
* @param context 上下文环境
* @param swipeRefreshLayout 下拉刷新布局
* @param listView 列表布局
* @param headerView 头部显示布局
* @param abnormalData 异常数据,上层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();
}
/**
* 下拉刷新数据
*/
public void refresh() {
mSwipeRefreshHelper.autoRefresh();
}
protected void configRefreshLayout() {
//!!!为了保证能够加载下一页,这个方法必须调用,且必须放在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); //修改内容
// } 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; //是否所有的数据都加载完成
@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);
/**
* 返回异常状态下的显示布局
* @param state
* @return
*/
public View getAbnormalView(BaseListModel.STATE state) {
switch (state){
case ASK_ED_CANNOT_ACCESS:
abnormalView.setText("无法访问网络");
break;
case ASK_ED_FAIL:
abnormalView.setText("无法访问服务器");
break;
case ASK_ED_ERROR:
abnormalView.setText("请求参数错误");
break;
case ASK_ED_EMPTY:
abnormalView.setText("数据为空");
break;
}
return abnormalView;
}
/**
* 加载list数据
*
* @param startIndex 起始数据项
* @param pageSize 预期加载多少项
* @return
*/
protected abstract ListNetResultInfo<O> onDoInBackgroundSafely(int startIndex, int pageSize);
/**
* 获取item布局中一个textview的id
*
* @return
*/
public abstract int getItemTextViewResourceId();
/**
* 获取对应Item布局的id
*
* @return
*/
public abstract int getItemLayoutId();
/**
* 将view和数据绑定
*
* @param position
* @param view
* @param parent
* @return
*/
public abstract View bindView(int position, View view, ViewGroup parent);
/**
* -------------------------
* END
* -------------------------
*/
}
Panda
2016-08-24