神刀安全网

关于新闻类应用快速开发框架的思考

序言

在上一篇博客 10分钟开发网易新闻首页的框架,我把我正在使用的框架拿出来分享了一下,但是部分同学告诉我源码看不懂,我觉得也有必要说说我对这个框架的思考过程。

流程

目前我主要开发的是新闻资讯类的应用,所以这个框架也主要是针对这类应用设计的,在这一类应用中,最重要的就是内容展示,一般都是以列表的形式展示数据,比如下图:

关于新闻类应用快速开发框架的思考
这里写图片描述

而这类页面还要有下拉刷新,上滑加载下一页,加载进度,错误重试等功能,但是这些功能都是公有的,而每个页面所不同的就是展示的内容的不同(废话),而这些内容又是以不同的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 "";     }   }

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);  }

有一个默认的实现

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);     }   }

大家可以通过重写SmartListFragment中的这个方法实现自己的替换。

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

上述的接口只是提供上拉加载更多的功能,而具体的逻辑每一个应用都各有不同为此,我使用这个接口实现对分页逻辑的解耦

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。zhuguohui/SmartDemo

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 关于新闻类应用快速开发框架的思考

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址