如何剝離Android頁面下拉刷新袁稽、加載下一頁等邏輯?

最近碰到一個新的頁面控制需求:下拉刷新如果失敗擒抛,listview上面的數(shù)據(jù)需要保留推汽,然后悲劇的發(fā)現(xiàn)之前寫的NetFragmentListNetFragment都不能覆蓋這種邏輯,又要重寫了歧沪。痛定思痛民泵,我發(fā)現(xiàn)問題的本質(zhì)在于 控制邏輯C 和 頁面展示V 沒有真正分離,因此決定先把邏輯從Fragment里面抽取出來槽畔。

一栈妆、建立狀態(tài)模型

所謂的邏輯,在這里是一個狀態(tài)機厢钧,包含的狀態(tài)如下:

  1. 請求數(shù)據(jù)狀態(tài):請求前鳞尔、請求中、請求結(jié)束
  2. 請求后的狀態(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市沼瘫,隨后出現(xiàn)的幾起案子抬纸,更是在濱河造成了極大的恐慌,老刑警劉巖晕鹊,帶你破解...
    沈念sama閱讀 223,126評論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件松却,死亡現(xiàn)場離奇詭異暴浦,居然都是意外死亡溅话,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評論 3 400
  • 文/潘曉璐 我一進店門歌焦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來飞几,“玉大人,你說我怎么就攤上這事独撇⌒寄” “怎么了?”我有些...
    開封第一講書人閱讀 169,941評論 0 366
  • 文/不壞的土叔 我叫張陵纷铣,是天一觀的道長卵史。 經(jīng)常有香客問我,道長搜立,這世上最難降的妖魔是什么以躯? 我笑而不...
    開封第一講書人閱讀 60,294評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮啄踊,結(jié)果婚禮上忧设,老公的妹妹穿的比我還像新娘。我一直安慰自己颠通,他們只是感情好址晕,可當(dāng)我...
    茶點故事閱讀 69,295評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著顿锰,像睡著了一般谨垃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上硼控,一...
    開封第一講書人閱讀 52,874評論 1 314
  • 那天乘客,我揣著相機與錄音,去河邊找鬼淀歇。 笑死易核,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的浪默。 我是一名探鬼主播牡直,決...
    沈念sama閱讀 41,285評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼缀匕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了碰逸?” 一聲冷哼從身側(cè)響起乡小,我...
    開封第一講書人閱讀 40,249評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饵史,沒想到半個月后满钟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,760評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡胳喷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,840評論 3 343
  • 正文 我和宋清朗相戀三年湃番,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吭露。...
    茶點故事閱讀 40,973評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡吠撮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出讲竿,到底是詐尸還是另有隱情泥兰,我是刑警寧澤,帶...
    沈念sama閱讀 36,631評論 5 351
  • 正文 年R本政府宣布题禀,位于F島的核電站鞋诗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏迈嘹。R本人自食惡果不足惜削彬,卻給世界環(huán)境...
    茶點故事閱讀 42,315評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望江锨。 院中可真熱鬧吃警,春花似錦、人聲如沸啄育。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挑豌。三九已至安券,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間氓英,已是汗流浹背侯勉。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評論 1 275
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铝阐,地道東北人址貌。 一個月前我還...
    沈念sama閱讀 49,431評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親练对。 傳聞我的和親對象是個殘疾皇子遍蟋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,982評論 2 361

推薦閱讀更多精彩內(nèi)容