如何剥离Android页面下拉刷新、加载下一页等逻辑?

最近碰到一个新的页面控制需求:下拉刷新如果失败,listview上面的数据需要保留,然后悲剧的发现之前写的NetFragmentListNetFragment都不能覆盖这种逻辑,又要重写了。痛定思痛,我发现问题的本质在于 控制逻辑C 和 页面展示V 没有真正分离,因此决定先把逻辑从Fragment里面抽取出来。

一、建立状态模型

所谓的逻辑,在这里是一个状态机,包含的状态如下:

  1. 请求数据状态:请求前、请求中、请求结束
  2. 请求后的状态有五种:
  • 无法访问网络;
  • 服务器连接失败;
  • 请求参数异常;
  • 数据为空;
  • 获取到有效数据,如果是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

    原文作者:沉思的Panda
    原文地址: https://www.jianshu.com/p/667cf70be2ad
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞