關于新聞類應用快速開發框架的思考

流程

目前我主要開發的是新聞資訊類的應用,所以這個框架也主要是針對這類應用設計的,在這一類應用中,最重要的就是內容展示,一般都是以列表的形式展示數據,比如下圖:

這里寫圖片描述

而這類頁面還要有下拉刷新,上滑加載下一頁,加載進度,錯誤重試等功能,但是這些功能都是公有的,而每個頁面所不同的就是展示的內容的不同(廢話),而這些內容又是以不同的item為基礎的。例如下面不同的item:

Item1

這里寫圖片描述

item2

這里寫圖片描述

而在軟件開發中,所不同的就是JSON數據的不同,JavaBean的不同,Layout的不同。大體的對應關系如下:

這里寫圖片描述

因此在實際開發中,最小的粒度就是一個Object。我的框架工作流程就是找到這些JsonObject然后轉化為對應的JavaBean傳統,實例對應的ViewHolder,然后將數據傳遞給對應的ViewHolder,ViewHolder綁定數據,最終一個新聞列表就顯示出來了,大概的流程如下:

這里寫圖片描述

實現

1. JSON數據的解析

在一般的開發流程中,我們會將服務器返回的Json數據直接轉換成一個JavaBean,然后再操作。在界面不是很復雜的情況下這么做無可厚非,但是在多個界面有相同的Item,又有部分不同的Item。這就會造成,每一個界面的數據都要有一個對應的JavaBean,而且這些解析都會固化在類中,或許可以通過繼承實現部分的復用,但是對于擴展性并不優化,三大設計原則中就有,組合優于繼承,因此我所做的就是降低粒度,即針對JsonObject,而不是全部的數據。對于如何定位,我自己設計了一種定位的描述符:

比如要定位到下面這個數組:

這里寫圖片描述

我在配置文件中是這么寫的

這里寫圖片描述

大框中括起來的最終會轉換為JsonConfig對象的素組,交給JsonAnalysisEngine 處理,而JsonAnalysisEngine 會循環遍歷然后處理每一個JsonConfig,首先會更具jsonLocation的位置對原來的數據進行過濾,找到需要的數據,最后自動轉化為對應的Javabean,關于使用大家看注釋吧,目前來看可能性能還不是很好,我在后續的版本可能會更換實現方式。

package com.zgh.smartlibrary.json;

import android.text.TextUtils;

import com.google.gson.Gson; import com.zgh.smartlibrary.config.JsonAnalysisConfig; import com.zgh.smartlibrary.util.GsonUtil;

import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject;

import java.util.ArrayList; import java.util.List;

/**

  • Created by zhuguohui on 2016/8/17. */ public class JsonAnalysisEngine { List<JsonAnalysisConfig> configItems = new ArrayList<>(); Gson gson = new Gson();

    public JsonAnalysisEngine(List<JsonAnalysisConfig> configItems) {

     this.configItems = configItems;
    

    }

    public JsonAnalysisEngine(JsonAnalysisConfig... configItems) {

     this.configItems.clear();
     for (JsonAnalysisConfig config : configItems) {
         this.configItems.add(config);
     }
    
    

    }

    private int listDataSize = 0;

    /**

    • 獲取數據 *
    • @param jsonStr
    • @return */ public List<Object> getData(String jsonStr) { listDataSize = 0; List<Object> data = new ArrayList<>(); //處理每一個JsonAnalysisConfig for (JsonAnalysisConfig item : configItems) {

       Object o = getDataFromJson(jsonStr, item);
       if (o != null) {
           //如果過JsonAnalysisConfig設置isListData為true則代表,其為List的主要數據,直接解析為List
           if (item.isListData()) {
               List list = (List) o;
               data.addAll(list);
               //記錄List的數據的數量,對分頁來說需要這個數量判斷是否還有下一頁
               listDataSize += list.size();
           } else {
               data.add(o);
           }
       }
      

      } return data; }

      public int getListDataSize() { return listDataSize; }

      private Object getDataFromJson(String jsonStr, JsonAnalysisConfig item) { Object o = null; String jsonLocation = item.getJsonLocation(); try {

       String jsonData = getJsonStringFromLocation(jsonStr, jsonLocation);
       if (!TextUtils.isEmpty(jsonData)) {
           //如果是[開頭的代表是一個數組,解析為對應的javabean的List
           if (jsonData.startsWith("[")) {
               o = GsonUtil.jsonToBeanList(jsonData, Class.forName(item.getType()));
           } else {
               o = GsonUtil.jsonToBean(jsonData, Class.forName(item.getType()));
           }
       }
      

      } catch (Exception e) {

       e.printStackTrace();
      

      } return o; }

      /**

    • 返回jsonLocation 所描述的String *
    • @param jsonStr 需要處理的jsonStr
    • @param jsonLocation 描述的jsonLocation
    • @return
    • @throws JSONException */ private String getJsonStringFromLocation(String jsonStr, String jsonLocation) throws JSONException { //這個方法會遞歸調用,每一次調用jsonLocation就會減少一層,當解析結束的時候jsonLocation就為空 if (TextUtils.isEmpty(jsonLocation)) {

       return jsonStr;
      

      } char a; //記錄接下來的操作,如果是{開頭代表解析一個對象,以[開頭代表解析一個數組 char op = 0; boolean haveFoundOP = false; int nameStart = 0, nameEnd = 0; //記錄需要解析的對象的名字,例如{news 代表解析一個叫做news的json對象,[news代表解析一個叫news的數組 String name; for (int i = 0; i < jsonLocation.length(); i++) {

       a = jsonLocation.charAt(i);
       if ('{' == a || '[' == a) {
           if (!haveFoundOP) {
               op = a;
               haveFoundOP = true;
               nameStart = i + 1;
           } else {
               nameEnd = i - 1;
               break;
           }
       }
      

      } if (nameStart != 0 && nameEnd != 0) {

       name = jsonLocation.substring(nameStart, nameEnd + 1);
       jsonLocation = jsonLocation.substring(nameEnd + 1);
      

      } else {

       name = jsonLocation.substring(nameStart);
       jsonLocation = "";
      

      } jsonStr = jsonStr.trim(); int index = -1; //如果name中包含:表示需要解析一個數組指定的部分,比如[news:0 代表解析名叫news的Json數組下的第一條數據。 if (name.indexOf(":") != -1) {

       String[] split = name.split(":");
       name = split[0];
       try {
           index = Integer.valueOf(split[1]);
       } catch (Exception e) {
       }
      
      

      } //如果原來的json字符串是以{開頭代表解析一個對象,否則解析一個數組 if (jsonStr.startsWith("{")) {

       JSONObject jsonObject = new JSONObject(jsonStr);
       if ('{' == op && jsonObject.has(name)) {
           return getJsonStringFromLocation(jsonObject.getJSONObject(name).toString(), jsonLocation);
       } else if ('[' == op) {
      
           if (index == -1) {
               return getJsonStringFromLocation(jsonObject.getJSONArray(name).toString(), jsonLocation);
           } else {
               return getJsonStringFromLocation(jsonObject.getJSONArray(name).getJSONObject(index).toString(), jsonLocation);
           }
       }
      

      } else {

       try {
           if (index != -1) {
               JSONArray array = new JSONArray(jsonStr);
               return array.getJSONObject(index).toString();
           } else {
               return jsonStr;
           }
       } catch (Exception e) {
           e.printStackTrace();
           return "";
       }
    }
    return "";
}


}</code></pre>

2.下拉刷新,上拉加載更多

為了以后的可擴展性,我把下拉刷新,上拉加載更多抽象成一個接口

package com.zgh.smartlibrary.manager;

import android.view.View; import android.widget.BaseAdapter; import android.widget.ListView;

import com.zgh.smartlibrary.page.IPagePolicy;

/**

  • 能提供下拉刷新,上拉加載更多的接口
  • Created by zhuguohui on 2016/9/5. */ public interface ListViewUpdateManger {

    /**

    • 返回ListView
    • @return */ ListView getListView();

      /**

    • 返回view
    • @return */ View getView();

      /**

    • 相應不同的狀態,比如沒有分頁信息就不顯示,加載更多等。
    • @param state */ void setState(IPagePolicy.PageState state);
void setAdapter(BaseAdapter adapter);

/**
 * 設置回調接口
 * @param listener
 */
void setUpdateListener(UpdateListener listener);

interface UpdateListener {
    void pullUp();
    void pullDown();
}

/**
 * 更新完成時回調,實現者可在這個方法中結束動畫
 */
void updateComplete();

/**
 * 手動更新
 * @param showAnimation 是否顯示動畫
 */
void update(boolean showAnimation);

}</code></pre>

有一個默認的實現

package com.zgh.smartlibrary.manager.impl;

import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView;

import com.handmark.pulltorefresh.library.PullToRefreshBase; import com.handmark.pulltorefresh.library.PullToRefreshListView; import com.zgh.smartlibrary.R; import com.zgh.smartlibrary.manager.ListViewUpdateManger; import com.zgh.smartlibrary.page.IPagePolicy; import com.zgh.smartlibrary.util.AppUtil; import com.zgh.smartlibrary.util.TimeUtil;

/**

  • Created by zhuguohui on 2016/9/5. */ public class PullToRefreshManger implements ListViewUpdateManger {

    private final TextView footerView; protected PullToRefreshListView mPullToRefreshView; protected ListView listView; private String mStrNextPageRetry = "加載失敗 點擊重試"; protected int LAYOUT_ID = R.layout.fragment_smart_list; private long mLastRefreshTime = 0; private boolean isUpdate = false; private View mBaseView = null;

    public PullToRefreshManger(Context context) {

     mBaseView = View.inflate(context, LAYOUT_ID, null);
     mPullToRefreshView = (PullToRefreshListView) mBaseView.findViewById(R.id.refreshView);
     mPullToRefreshView.setMode(PullToRefreshBase.Mode.BOTH);
     //配置加載更多文字
     mPullToRefreshView.getLoadingLayoutProxy(false, true).setPullLabel("上拉加載更多");
     mPullToRefreshView.getLoadingLayoutProxy(false, true).setRefreshingLabel("正在加載");
     mPullToRefreshView.getLoadingLayoutProxy(false, true).setReleaseLabel("釋放加載更多");
     //加載更多的借口
     mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener2<ListView>() {
         @Override
         public void onPullDownToRefresh(final PullToRefreshBase<ListView> refreshView) {
             if (listener != null) {
                 isUpdate = true;
                 listener.pullUp();
             }
         }
    
         @Override
         public void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {
    
             if (listener != null) {
                 isUpdate = false;
                 listener.pullDown();
             }
         }
     });
     mPullToRefreshView.setOnPullEventListener(new PullToRefreshBase.OnPullEventListener<ListView>() {
         @Override
         public void onPullEvent(PullToRefreshBase<ListView> refreshView,
                                 PullToRefreshBase.State state,
                                 PullToRefreshBase.Mode direction) {
             if ((state == PullToRefreshBase.State.PULL_TO_REFRESH ||
                     state == PullToRefreshBase.State.REFRESHING || state == PullToRefreshBase.State.MANUAL_REFRESHING)
                     && direction == PullToRefreshBase.Mode.PULL_FROM_START) {
                 if (mLastRefreshTime != 0L) {
                     mPullToRefreshView.getLoadingLayoutProxy(true, false)
                             .setLastUpdatedLabel(TimeUtil.format(mLastRefreshTime)
                                     + "更新");
                 }
             }
         }
     });
    
     //配置ListView
     listView = mPullToRefreshView.getRefreshableView();
     listView.setFooterDividersEnabled(false);
     listView.setOverScrollMode(View.OVER_SCROLL_NEVER);
     listView.setDivider(null);
    //添加footview
    footerView = (TextView) View.inflate(context, R.layout.view_bottom, null);
    footerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, AppUtil.dip2px(context, 35)));
    //要隱藏footview其外部必須再包裹一層layout
    LinearLayout footerViewParent = new LinearLayout(context);
    footerViewParent.addView(footerView);
    footerView.setVisibility(View.GONE);
    footerView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (footerView.getText().equals(mStrNextPageRetry)) {
                // footerView.setVisibility(View.GONE);
                mPullToRefreshView.setMode(PullToRefreshBase.Mode.PULL_FROM_END);
                mPullToRefreshView.setRefreshing(true);
            }
        }
    });
    listView.addFooterView(footerViewParent);
}

@Override
public ListView getListView() {
    return listView;
}

@Override
public View getView() {
    return mBaseView;
}

@Override
public void setState(IPagePolicy.PageState state) {
    switch (state) {
        case NO_MORE:
            footerView.setText("沒有更多了");
            footerView.setVisibility(View.VISIBLE);
            mPullToRefreshView.setMode(PullToRefreshBase.Mode.PULL_FROM_START);
            break;
        case HAVE_MORE:
            footerView.setVisibility(View.GONE);
            mPullToRefreshView.setMode(PullToRefreshBase.Mode.BOTH);
            break;
        case NO_PAGE:
            footerView.setVisibility(View.GONE);
            mPullToRefreshView.setMode(PullToRefreshBase.Mode.DISABLED);
            break;
        case LOAD_ERROR:
            footerView.setText(mStrNextPageRetry);
            footerView.setVisibility(View.VISIBLE);
            break;
        default:
            throw new IllegalArgumentException("Specified state is not supported state=" + state);
    }
}


@Override
public void setAdapter(BaseAdapter adapter) {
    //設置adapter
    listView.setAdapter(adapter);
}

UpdateListener listener;

@Override
public void setUpdateListener(UpdateListener listener) {
    this.listener = listener;
}

@Override
public void updateComplete() {
    if (isUpdate) {
        mLastRefreshTime = System.currentTimeMillis();
    }
    mPullToRefreshView.onRefreshComplete();
}

@Override
public void update(boolean showAnimation) {
    mPullToRefreshView.setRefreshing(false);
}


}</code></pre>

大家可以通過重寫SmartListFragment中的這個方法實現自己的替換。

/**

 * 獲取具有下拉刷新,上拉加載更多功能的接口
 * @param context
 * @return
 */
protected  ListViewUpdateManger getUpdateManager(Context context){
    return new PullToRefreshManger(context);
}</code></pre> 

上述的接口只是提供上拉加載更多的功能,而具體的邏輯每一個應用都各有不同為此,我使用這個接口實現對分頁邏輯的解耦

package com.zgh.smartlibrary.page;

import com.zgh.smartlibrary.net.NetRequest;

/**
 * Created by zhuguohui on 2016/9/2.
 */
public interface IPagePolicy {

    /**
     * 獲取分頁的狀態
     *
     * @param dataSize 當前頁的item數量
     * @param data     需要解析的json數據
     */
    PageState getPageState(int dataSize, String data);


    /**
     * 更具index獲取網絡請求
     *
     * @param index
     * @return
     */
    NetRequest getNetRequestByPageIndex(int index);

    /**
     * 設置一個基本url,例如第一頁的url,下一頁在此基礎上改變就行了
     * @param baseURL
     */
    void setBaseURL(String baseURL);

    /**
     * 分頁的狀態
     */
    enum PageState {
        //沒有分頁信息,還有下一頁,沒有更多,加載下一頁失敗
        NO_PAGE, HAVE_MORE, NO_MORE, LOAD_ERROR
    }
}

我的實閑是這樣的

package com.zgh.smartdemo.page;

import com.zgh.smartdemo.bean.PageInfo;
import com.zgh.smartlibrary.config.JsonAnalysisConfig;
import com.zgh.smartlibrary.json.JsonAnalysisEngine;
import com.zgh.smartlibrary.net.NetRequest;
import com.zgh.smartlibrary.page.IPagePolicy;

import java.util.List;

/**
 * Created by zhuguohui on 2016/9/10 0010.
 */
public class DemoPagePolicy implements IPagePolicy {
    //用于獲取分頁信息的JsonAnalysisEngine
    protected JsonAnalysisEngine pageEngine;
    private int mPageSize;
    private String mBaseUrl = "";

    public DemoPagePolicy() {
        //每頁數量為5
        mPageSize = 5;
        JsonAnalysisConfig config = new JsonAnalysisConfig();
        config.setJsonLocation("{response{page_info");
        config.setType(PageInfo.class.getName());
        pageEngine = new JsonAnalysisEngine(config);
    }


    @Override
    public PageState getPageState(int dataSize, String data) {
        PageState state;
        List<Object> data1 = pageEngine.getData(data);
        PageInfo page_info = data1 != null && data1.size() > 0 ? (PageInfo) data1.get(0) : null;
        //如果分頁信息為空的話,表示不需要分頁即NO_PAGE
        if (page_info != null) {
            try {
                //如果當前頁的數量小于每頁的數量,表示已到最后一頁
                int count = Integer.valueOf(page_info.getPage_count());
                int page_index = Integer.valueOf(page_info.getPage_index());
                if (count == page_index + 1 || dataSize < mPageSize) {
                    state = PageState.NO_MORE;
                } else {
                    state = PageState.HAVE_MORE;
                }
            } catch (Exception e) {
                e.printStackTrace();
                state = PageState.NO_MORE;
            }
        } else {
            state = PageState.NO_PAGE;
        }
        return state;
    }

    @Override
    public NetRequest getNetRequestByPageIndex(int index) {
        //此處改變的是url地址,具體項目具體分析。
        NetRequest request=new NetRequest();
        String url = mBaseUrl;
        if (index != 0) {
                url = mBaseUrl+"_"+index;
        }
        request.setUrl(url);
        return request;
    }

    @Override
    public void setBaseURL(String baseURL) {
        mBaseUrl = baseURL;
    }

}

我的分頁信息在這里

這里寫圖片描述

大家更具自己的項目情況來實現自己的分頁策略,只需要覆蓋SmartListFragment中的這個方法就行了

/**
     * 獲取分頁策略
     * @return
     */
    protected abstract IPagePolicy getPagePolicy();

3.狀態切換

此處使用的是張鴻洋的LoadingAndRetryManager,具體的用法如下參考這里 LoadingAndRetryManager

我簡單說一下用法,在Application的Oncreate中設置默認的樣式

/**
 * Created by zhuguohui on 2016/9/10 0010.
 */
public class DemoApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        LoadingAndRetryManager.BASE_RETRY_LAYOUT_ID = R.layout.base_retry;
        LoadingAndRetryManager.BASE_LOADING_LAYOUT_ID = R.layout.base_loading;
        LoadingAndRetryManager.BASE_EMPTY_LAYOUT_ID = R.layout.base_empty;
    }
}

如果某個Fragment有特殊需求,覆蓋這個方法就行了

protected OnLoadingAndRetryListener createLoadingAndRetryListener() {
        return null;
    }

4.網絡請求

目前的網絡框架有很多,但是我們不應該依賴于具體的某一個框架,而是采用面向接口編程的實現,把網絡請求這塊與具體的實現分開。于是我定義了兩個接口,一個用于網絡請求,一個用于網絡請求的處理:

package com.zgh.smartlibrary.net;

import java.util.HashMap;
import java.util.Map;

/**
 * 代表網路請求的接口
 * Created by zhuguohui on 2016/9/6.
 */
public class NetRequest {
    //地址
    String url;
    //請求方式
    METHOD method;
    //緩存方式
    CACHE cache;
    //參數
    Map<String, String> params;
    //結果回調
    NetResultListener resultListener;
    //數據
    Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public NetResultListener getResultListener() {
        return resultListener;
    }

    public NetRequest setResultListener(NetResultListener resultListener) {
        this.resultListener = resultListener;
        return this;
    }

    public String getUrl() {
        return url;
    }

    public NetRequest setUrl(String url) {
        this.url = url;
        return this;
    }

    public METHOD getMethod() {
        return method;
    }

    public NetRequest setMethod(METHOD method) {
        this.method = method;
        return this;
    }

    public CACHE getCache() {
        return cache;
    }

    public NetRequest setCache(CACHE cache) {
        this.cache = cache;
        return this;
    }

    public Map<String, String> getParams() {
        return params;
    }

    public void setParams(Map<String, String> params) {
        this.params = params;
    }

    public NetRequest addParams(String key, String value) {
        if (params == null) {
            params = new HashMap<>();
        }
        params.put(key, value);
        return this;
    }

    public enum METHOD {
        GET, POST
    }

  public  enum CACHE {
        //文件緩存,內存緩存,不需要緩存。
        FILE, MEMORY, NO_CACHE
    }

    public interface NetResultListener {
        void onSuccess(NetRequest netRequest);
        void onError(String msg);
    }

    @Override
    public boolean equals(Object o) {
        return super.equals(o);
    }
}

一個用于處理請求:

package com.zgh.smartlibrary.net;

/**
 * Created by zhuguohui on 2016/9/6.
 */
public interface NetRequestHandler {
    /**
     * 處理網路請求
     * @param netRequest
     */
    void handleNetRequest(NetRequest netRequest);

    /**
     * 取消網絡請求
     * @param netRequest
     */
    void cancelNetRequest(NetRequest netRequest);

}

有一個采用張鴻洋的OkHttpUtil的默認實現

package com.zgh.smartlibrary.net.impl;

import android.content.Context;


import com.zgh.smartlibrary.net.NetRequest;
import com.zgh.smartlibrary.net.NetRequestHandler;
import com.zgh.smartlibrary.util.FileUtil;
import com.zhy.http.okhttp.OkHttpUtils;
import com.zhy.http.okhttp.builder.GetBuilder;
import com.zhy.http.okhttp.builder.HasParamsable;
import com.zhy.http.okhttp.builder.OkHttpRequestBuilder;
import com.zhy.http.okhttp.builder.PostFormBuilder;
import com.zhy.http.okhttp.callback.StringCallback;

import java.util.Map;

import okhttp3.Call;

/**
 * Created by zhuguohui on 2016/9/6.
 */
public class SmartNetRequestHandler implements NetRequestHandler {
    private final Context mContext;
    private String HTTP_HEAD = "http";
    private String HTTPS_HEAD = "https";
    private String RAW_HEAD="raw://";

    public SmartNetRequestHandler(Context context){
        mContext=context;
    }

    @Override
    public void handleNetRequest(final NetRequest netRequest) {
        String url = netRequest.getUrl();

        boolean isHttpRequest = false;
        if (url != null && url.length() > 5) {

            if (url.toLowerCase().startsWith(HTTP_HEAD) || url.toLowerCase().startsWith(HTTPS_HEAD)) {
                isHttpRequest = true;
            }
        }
        if(netRequest.getMethod()==null){
            netRequest.setMethod(NetRequest.METHOD.GET);
        }
        if (isHttpRequest) {
            GetBuilder getBuilder = null;
            PostFormBuilder postFormBuilder = null;
            OkHttpRequestBuilder requestBuilder;
            HasParamsable hasParamsable;
            switch (netRequest.getMethod()) {
                case GET:
                    getBuilder = OkHttpUtils.get();
                    break;
                case POST:
                    postFormBuilder = OkHttpUtils.post();
                    break;
            }
            requestBuilder = getBuilder != null ? getBuilder : postFormBuilder;
            if (requestBuilder == null) {
                onError(netRequest, "不支持的協議!");
                return;
            }
            hasParamsable = getBuilder != null ? getBuilder : postFormBuilder;
            requestBuilder.url(url);
            Map<String, String> params = netRequest.getParams();
            if (params != null && params.size() > 0) {
                for (String key : params.keySet()) {
                    hasParamsable.addParams(key, params.get(key));
                }
            }
            requestBuilder.build().execute(new StringCallback() {
                @Override
                public void onError(Call call, Exception e, int id) {
                    SmartNetRequestHandler.this.onError(netRequest, e.getMessage());
                }

                @Override
                public void onResponse(String response, int id) {
                    onSuccess(netRequest,response);
                }
            });
        } else {
            if(url.toLowerCase().startsWith(RAW_HEAD)){
                String rawName = url.substring(RAW_HEAD.length());
                String s = FileUtil.readRaw(mContext, rawName);
                onSuccess(netRequest, s);
            }else{
                onError(netRequest,"不支持的協議!");
                return;
            }
        }

    }

    public void onError(NetRequest request, String msg) {
        if (request != null && request.getResultListener() != null) {
            request.getResultListener().onError(msg);
        }
    }

    public void onSuccess(NetRequest request, Object data) {
        if (request != null && request.getResultListener() != null) {
            request.setData(data);
            request.getResultListener().onSuccess(request);
        }
    }

    @Override
    public void cancelNetRequest(NetRequest netRequest) {

    }
}

如果以后有其他的網絡框架出來了,大家可以實現自己的NetRequestHandler 并替換默的,覆蓋這個方法就行了SmartListFragment。

/**
     * 獲取網絡請求處理器
     * @param context
     * @return
     */
    protected NetRequestHandler getNetRequestHandler(Context context) {
        return new SmartNetRequestHandler(context);
    }

另外,為了方便大家對網絡請求的統一修改,我才用責任鏈的方式實現了一個網絡請求修改器。

package com.zgh.smartlibrary.net;

/**
 * 用于對網絡請求就行修改
 * Created by zhuguohui on 2016/9/6.
 */
public interface NetRequestModifiers {
    NetRequest modifyNetRequest(NetRequest request);
}

使用的時候只需要在覆蓋initNetRequestModifiers然后調用addNetRequestModifiers加入自己的NetRequestModifiers就行了,這個可以功能可以實現給所以的網絡請求加統一的Token等等。

@Override
    protected void initNetRequestModifiers() {
        addNetRequestModifiers(new NetRequestModifiers() {
            @Override
            public NetRequest modifyNetRequest(NetRequest request) {
                return request;
            }
        });
    }

補充一下,要想刷新界面調用這個方法就行了

/**
     * 加載頁面數據
     * @param pageIndex 
     */
    protected void loadListData(int pageIndex) {
        requestIndex = pageIndex;
        isUpdate = pageIndex == FIRST_PAGE_INDEX;
        NetRequest request  = pagePolicy.getNetRequestByPageIndex(pageIndex);
        request.setResultListener(this);
        if (isUpdate && adapterManager.getDataSize() == 0) {
            showLoading();
        }
        if (isUpdate) {
            //緩存首頁
            request.setCache(NetRequest.CACHE.FILE);
        }
        listDataRequest = request;
        loadData(request);
    }

要想加載自己的網絡請求,使用這個方法

/**
     * 加載網絡請求
     * @param netRequest
     */
    protected void loadData(NetRequest netRequest) {
        netRequest = modifyNetRequest(netRequest);
        netRequestHandler.handleNetRequest(netRequest);
    }

5.數據展示

數據的展示關鍵點是Adapter,目前用的還是ListView,不過需求都能滿足。

package com.zgh.smartlibrary.adapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by 朱國輝
 * Date: 2015/12/5
 * Time: 22:26
 *
 */
public abstract class SmartAdapter<E> extends BaseAdapter {

    protected Context mContext;
    private List<E> data;

    public Map<Integer, SmartViewHolder> holderMap = new HashMap<>();

    public SmartAdapter(Context ctx, List<E> data,
                        SmartViewHolder... holders) {
        this.mContext = ctx;
        this.data = data;
        if (holders != null && holders.length > 0) {
            for (int i = 0; i < holders.length; i++) {
                holders[i].setType(i);
                holderMap.put(holders[i].getViewType(), holders[i]);
            }
        } else {
            throw new IllegalArgumentException("SmartViewHolder 不能為空");
        }

    }

    public SmartAdapter(Context ctx, List<E> data,
                        List<SmartViewHolder> holders) {
        this.mContext = ctx;
        this.data = data;
        int i = 0;
        if (holders != null && holders.size() > 0) {
            for (SmartViewHolder holder : holders) {
                holder.setType(i++);
                holderMap.put(holder.getViewType(), holder);
            }
        } else {
            throw new IllegalArgumentException("SmartViewHolder 不能為空");
        }

    }

    @Override
    public E getItem(int position) {
        if (!isEmpty(data)) {
            return data.get(position);
        }
        return null;
    }

    @Override
    public int getViewTypeCount() {
        return holderMap.size();
    }


    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public int getCount() {
        if (!isEmpty(data)) {
            return data.size();
        }
        return 0;
    }


    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        SmartViewHolder holder;
        if (convertView == null) {
            int type = getItemViewType(position);
            convertView = LayoutInflater.from(mContext).inflate(holderMap.get(type).getLayoutId(), parent, false);
            holder = buildHolder(convertView, type);
            convertView.setTag(holder);
        } else {
            holder = (SmartViewHolder) convertView.getTag();
        }

        // 避免Item在滾動中出現黑色背景
        convertView.setDrawingCacheEnabled(false);
        E item = getItem(position);
        holder.setContentView(convertView);
        holder.updateView(mContext, item);
        return convertView;
    }

    /**
     * 用于自動綁定view
     * @param convertView
     * @param type
     * @return
     */
    private SmartViewHolder buildHolder(View convertView, int type) {
        SmartViewHolder holder;
        try {
            holder = holderMap.get(type).getClass().newInstance();
            List<Field> fields = getViewFields(holder.getClass());
            for (Field f : fields) {
                String name = f.getName();
                f.setAccessible(true);
                // ViewHolder的屬性,不論類型都初始化賦值
                f.set(holder, convertView.findViewById(getId(name)));
            }
        } catch (Exception e) {
            throw new RuntimeException("holder初始化出錯    " + e);
        }
        return holder;
    }

    /**
     * ViewHolder中只有是View的子類的成員變量才會被初始化
     * @param clazz
     * @return
     */
    private List<Field> getViewFields(Class clazz) {
        List<Field> fields = new ArrayList<>();
        while (clazz != null && clazz != SmartViewHolder.class) {
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field f : declaredFields) {
                if (isViewField(f)) {
                    fields.add(f);
                }
            }
            clazz = clazz.getSuperclass();
        }
        return fields;
    }

    private boolean isViewField(Field f) {
        Class<?> fType = f.getType();
        boolean isView = false;
        Class sclass = fType;
        while (sclass != null && sclass != View.class) {
            sclass = sclass.getSuperclass();
        }
        if (sclass == View.class) {
            isView = true;
        }
        return isView;
    }


    public void addItems(List<E> extras) {
        if (isEmpty(extras)) {
            return;
        }
        data.addAll(getCount(), extras);
        notifyDataSetChanged();
    }

    @Override
    public int getItemViewType(int position) {
        Collection<SmartViewHolder> holders = holderMap.values();
        for (SmartViewHolder holder : holders) {
            if (holder.isMyType(data.get(position))) {
                return holder.getViewType();
            }
        }
        throw new RuntimeException("沒有對應的 SmartViewHolder position=" + position + " item=" + data.get(position));
    }


    /**
     * Some General Functions
     */
    private boolean isEmpty(List<?> list) {
        return (list == null || list.size() == 0);
    }


    public int getId(String name) {
        try {
            return mContext.getResources().getIdentifier(name, "id", mContext.getPackageName());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static abstract class SmartViewHolder<E> {
        int type;
        private View contentView;

        /**
         * 獲取該VIewHolder對應的Type
         *
         * @return
         */
        public final int getViewType() {
            return type;
        }

        /**
         * 判斷是否是自己處理的數據類型
         *
         * @param item
         * @return
         */
        public abstract boolean isMyType(E item);

        public void setType(int type) {
            this.type = type;
        }

        /**
         * 獲取對應的布局id
         *
         * @return
         */
        public abstract int getLayoutId();

        public abstract void updateView(Context context, E item);

        public void setContentView(View contentView) {
            this.contentView = contentView;
        }

        public View getContentView() {
            return this.contentView;
        }

    }
}

6.界面定制

很多時候一個界面中并不是只需要一個ListView就能解決了,還需要有一個其他的內容,為了偷懶,我就通過使用占位符的信息來實現對自定義界面的需求。下面是我的占位符:

這里寫圖片描述

然后覆蓋SmartListFragment這個方法,返回自定義布局的ID

/**
     * 獲取自定義布局的layoutID
     * @return
     */
    protected int getLayoutID() {
        return 0;
    }

最后的處理在這里,不難就是需要慢慢寫。

package com.zgh.smartlibrary.util;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;

import com.zgh.smartlibrary.R;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by zhuguohui on 2016/9/6.
 */
public class LayoutUtil {
    public static View getBaseView(Context context, int layoutID, View mView, ListView listView) {
        if (layoutID != 0) {
            View warpView = View.inflate(context, layoutID, null);
            View holderView = warpView.findViewById(R.id.ListViewHolder);
            //判斷是否是LinearLayout
            if (holderView instanceof LinearLayout) {
                LinearLayout holder = (LinearLayout) holderView;
                //獲取id為ListViewContent的view,如果沒有表示,子view全部都要添加為heardView
                View contentView = holder.findViewById(R.id.ListViewContent);
                List<View> headerViews = new ArrayList<>();
                List<View> footViews = new ArrayList<>();
                List viewList = headerViews;
                for (int i = 0; i < holder.getChildCount(); i++) {
                    View childView = holder.getChildAt(i);
                    if (childView == contentView) {
                        viewList = footViews;
                        continue;
                    }
                    viewList.add(childView);
                }
                handleHeaderAndFooterView(listView, context, headerViews, footViews);
            }

            ViewGroup parent = (ViewGroup) holderView.getParent();
            if (parent != null) {
                int index = 0;
                for (int i = 0; i < parent.getChildCount(); i++) {
                    if (parent.getChildAt(i) == holderView) {
                        index = i;
                        break;
                    }
                }
                parent.removeView(holderView);
                ViewGroup.LayoutParams params = holderView.getLayoutParams();
                mView.setLayoutParams(params);
                parent.addView(mView, index);
                mView = parent;
            }
        }

        return mView;
    }

    private static void handleHeaderAndFooterView(ListView listView, Context context, List<View> headerViews, List<View> footViews) {
        for (View view : headerViews) {
            LinearLayout ViewParent = new LinearLayout(context);
            if (view.getParent() != null) {
                ViewGroup group = (ViewGroup) view.getParent();
                group.removeView(view);
            }
            ViewParent.addView(view);
            listView.addHeaderView(ViewParent);
        }

        for (View view : footViews) {
            LinearLayout ViewParent = new LinearLayout(context);
            if (view.getParent() != null) {
                ViewGroup group = (ViewGroup) view.getParent();
                group.removeView(view);
            }
            ViewParent.addView(view);
            listView.addFooterView(ViewParent);
        }
    }
}

當自定義布局填充好了以后,通過這個方法可以拿到View的引用

@Override
    protected void onViewInit() {
        //記得使用父類的findViewById方法。
        findViewById(R.id.tv_head1).setOnClickListener(this);
        findViewById(R.id.tv_head2).setOnClickListener(this);
        findViewById(R.id.tv_footer1).setOnClickListener(this);
        findViewById(R.id.tv_fixed_head).setOnClickListener(this);
        findViewById(R.id.tv_fload_view).setOnClickListener(this);
    }

7.數據修改

對于返回的數據在現實之前,是可以修改的,也是使用責任鏈的模式實現的。

interface DataModifiers {
        List<Object> modifyData(List<Object> data, boolean update);
    }

使用的時候這樣使用覆蓋SmartListFragment的getDataModifiers方法返回自己的數據。

@Override
    protected AdapterManager.DataModifiers[] getDataModifiers() {
        //過濾數據
        return new AdapterManager.DataModifiers[]{new NewsDataModifiers()};
    }

我的demo中有一個例子,因為第二頁數據中包含有,banner數據等不需要的東西,所以過濾了一下

/**
 * Created by yuelin on 2016/9/6.
 */
public class NewsDataModifiers implements AdapterManager.DataModifiers {
    @Override
    public List<Object> modifyData(List<Object> data, boolean update) {
        if (!update) {
            List<Object> newsList = new ArrayList<>();
            for (Object object : data) {
                if (object instanceof NewsItem) {
                    newsList.add(object);
                }
            }
            data.clear();
            data.addAll(newsList);
        }
        return data;
    }
}

總結

這個框架也許還有很多問題,但對于我來說確實是不小的提升,特別是用到許多學過的設計模式,也一直在思考怎么解耦,怎么對修改封閉,對拓展開放等原則。寫完這個框架大概才有一點點軟件設計師的感覺,終于是在設計一些東西了,前路怎樣我不知道,但是希望我能用心的做好每一件事,用心的寫程序,而不是為了圖完成工作,既然都看到這里了,去GitHub上給我來個start。

 

 

來自:http://www.jianshu.com/p/3f9daa32ad98

 

 本文由用戶 hrlj1816 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!