序言

在上一篇博客 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<>();//处理每一个JsonAnalysisConfigfor (JsonAnalysisConfig item : configItems) {Object o = getDataFromJson(jsonStr, item);if (o != null) {//如果过JsonAnalysisConfig设置isListData为true则代表,其为List的主要数据,直接解析为Listif (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的Listif (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>() {@Overridepublic void onPullDownToRefresh(final PullToRefreshBase<ListView> refreshView) {if (listener != null) {isUpdate = true;listener.pullUp();}}@Overridepublic void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {if (listener != null) {isUpdate = false;listener.pullDown();}}});mPullToRefreshView.setOnPullEventListener(new PullToRefreshBase.OnPullEventListener<ListView>() {@Overridepublic 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)+ "更新");}}}});//配置ListViewlistView = mPullToRefreshView.getRefreshableView();listView.setFooterDividersEnabled(false);listView.setOverScrollMode(View.OVER_SCROLL_NEVER);listView.setDivider(null);//添加footviewfooterView = (TextView) View.inflate(context, R.layout.view_bottom, null);footerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, AppUtil.dip2px(context, 35)));//要隐藏footview其外部必须再包裹一层layoutLinearLayout footerViewParent = new LinearLayout(context);footerViewParent.addView(footerView);footerView.setVisibility(View.GONE);footerView.setOnClickListener(new View.OnClickListener() {@Overridepublic 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);}@Overridepublic ListView getListView() {return listView;}@Overridepublic View getView() {return mBaseView;}@Overridepublic 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);}}@Overridepublic void setAdapter(BaseAdapter adapter) {//设置adapterlistView.setAdapter(adapter);}UpdateListener listener;@Overridepublic void setUpdateListener(UpdateListener listener) {this.listener = listener;}@Overridepublic void updateComplete() {if (isUpdate) {mLastRefreshTime = System.currentTimeMillis();}mPullToRefreshView.onRefreshComplete();}@Overridepublic 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 {//用于获取分页信息的JsonAnalysisEngineprotected JsonAnalysisEngine pageEngine;private int mPageSize;private String mBaseUrl = "";public DemoPagePolicy() {//每页数量为5mPageSize = 5;JsonAnalysisConfig config = new JsonAnalysisConfig();config.setJsonLocation("{response{page_info");config.setType(PageInfo.class.getName());pageEngine = new JsonAnalysisEngine(config);}@Overridepublic 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_PAGEif (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;}@Overridepublic NetRequest getNetRequestByPageIndex(int index) {//此处改变的是url地址,具体项目具体分析。NetRequest request=new NetRequest();String url = mBaseUrl;if (index != 0) {url = mBaseUrl+"_"+index;}request.setUrl(url);return request;}@Overridepublic 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 {@Overridepublic 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);}@Overridepublic 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;}@Overridepublic 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() {@Overridepublic void onError(Call call, Exception e, int id) {SmartNetRequestHandler.this.onError(netRequest, e.getMessage());}@Overridepublic 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);}}@Overridepublic 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等等。

@Overrideprotected void initNetRequestModifiers() {addNetRequestModifiers(new NetRequestModifiers() {@Overridepublic 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 不能为空");}}@Overridepublic E getItem(int position) {if (!isEmpty(data)) {return data.get(position);}return null;}@Overridepublic int getViewTypeCount() {return holderMap.size();}@Overridepublic long getItemId(int position) {return position;}@Overridepublic int getCount() {if (!isEmpty(data)) {return data.size();}return 0;}@Overridepublic 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();}@Overridepublic 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);//判断是否是LinearLayoutif (holderView instanceof LinearLayout) {LinearLayout holder = (LinearLayout) holderView;//获取id为ListViewContent的view,如果没有表示,子view全部都要添加为heardViewView 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的引用

 @Overrideprotected 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方法返回自己的数据。

@Overrideprotected AdapterManager.DataModifiers[] getDataModifiers() {//过滤数据return new AdapterManager.DataModifiers[]{new NewsDataModifiers()};}

我的demo中有一个例子,因为第二页数据中包含有,banner数据等不需要的东西,所以过滤了一下


/*** Created by yuelin on 2016/9/6.*/
public class NewsDataModifiers implements AdapterManager.DataModifiers {@Overridepublic 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

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

  1. 澎湃新闻产品总监首度分享,如何快速在新闻类APP中异军突围?一年时间进入前4名...

    2014年7月22日,由上海报业集团打造的澎湃新闻客户端正式上线.作为首批试水新媒体的国家队纸媒,自诞生之日起,澎湃新闻一直在媒体的聚光灯下,无论是产品设计.UI还是运营模式,都收到广泛关注,褒贬不一 ...

  2. android的快速开发框架集合

    android的快速开发框架集合 出自:http://blog.csdn.net/shulianghan/article/details/18046021 1.Afinal  (快速开发框架) 简介: ...

  3. 微信快速开发框架(六)-- 微信快速开发框架(WXPP QuickFramework)V2.0版本上线--源码已更新至github...

    4月28日,已增加多媒体上传及下载API,对应MediaUploadRequest和MediaGetRequest ------------------------------------------ ...

  4. [开源]OSharpNS - .net core 快速开发框架 - 简介

    OSharpNS全称OSharp Framework with .NetStandard2.0,是一个基于.NetStandard2.0开发的一个.NetCore快速开发框架.这个框架使用最新稳定版的 ...

  5. 【开源】OSharpNS,轻量级.net core快速开发框架发布

    OSharpNS简介 OSharp Framework with .NetStandard2.0(OSharpNS)是OSharp的以.NetStandard2.0为目标框架,在AspNetCore的 ...

  6. Web经典BS快速开发框架,强大后台+简洁UI一体化开发工具

    本框架旨在为.NET开发人员提供一个Web后台快速开发框架,采用本框架,能够极大的提高项目开发效率. 整个框架包括三个版本:.net,.net core,java(开发中) 以上三个版本中,.NET为 ...

  7. jeeplus快速开发框架

    刚找了份新工作,本来想走前端开发路线的,新项目算是有接触,但是没人带,一切都得自己学,而且岗位工作是研发经理,目前却是只有我一个,正在思考自己能力是够能胜任,考虑是不是要继续折腾了.工作不好找,换行更 ...

  8. 善假于物,利用工具2天开发一款完整新闻类iOS app

    题外话: 此篇文章以一个iOS新手的角度解释一款新闻类iOS APP诞生的过程,详细介绍在这过程中碰到的问题和我的解决思路.欢迎大家指正. 菜单界面: 主界面: 详细页面: 关于我们页面: 初期的想法 ...

  9. 推荐一个 Java 接口快速开发框架

    欢迎关注方志朋的博客,回复"666"获面试宝典 今天给小伙伴们介绍一个Java接口快速开发框架-magic-api 简介 magic-api 是一个基于 Java 的接口快速开发框 ...

最新文章

  1. R语言使用plotly绘制3D散点图实战
  2. 淘宝2011.9.21校园招聘会笔试题+答案
  3. onInterceptTouchEvent和onTouchEvent调用时序
  4. Log4j详细介绍(七)----日志格式化器Layout
  5. 本地文件夹如何断开svn连接
  6. Keil 编译太慢怎么办?教你一招,提速10倍
  7. docker启动elasticsearch——ERROR: Elasticsearch did not exit normally - check the logs at xxx
  8. python redis订阅_Python 学习笔记 - Redis
  9. (二十)java版spring cloud+spring boot 社交电子商务平台-spring cloud构建全球多租户分布式微服务部署的方案...
  10. java编码规范日常积累(持续更新)
  11. 机器学习与模式识别期末试题回忆
  12. 【精华帖】PS拼接图片最简单教程
  13. 深圳和信中欧金融科技研究院开业,着力打造金融科技高地项目
  14. 【FeatherNets】《FeatherNets:Convolutional Neural Networks as Light as Feather for Face Anti-spoofing》
  15. flask session permanent
  16. 多图!2020年互联网大厂中秋礼盒PK!阿里走情怀,腾讯更复古,最走心的是.........
  17. centos7 安装 mysql5.5_CentOs7 安装 Mysql5.7
  18. spring源码故事-面筋哥IoC容器的一天(上)
  19. Java大象进冰箱线程_把大象装进冰箱:HTTP传输大文件的方法
  20. php lodop,打印控件Lodop6.0版发布

热门文章

  1. SAP权限检查SU53
  2. oracle 京东,【京东工资】oracle dba待遇-看准网
  3. [《人件》摘录]: 生产力:赢得战役和输掉战争
  4. libco源码解析(2) 创建协程,co_create
  5. 无人驾驶汽车感知综述
  6. 【python小练】0014题 和 0015 题
  7. Naturehike和Bluetti成为Lazada户外露营之需
  8. OpenAI刚融资100亿,DeepMind CEO急了?呼吁AI圈减少科研竞赛!
  9. jira 切换 语言_JIRA中的标记语言的语法参考
  10. webp学习http://isux.tencent.com/introduction-of-webp.html