如何剥离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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容